mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-04-01 06:41:41 +00:00
Product Import v3 with asynchronous processing
Fixed spec Quick spec tweak
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
angular.module("ofn.admin").controller "ImportFeedbackCtrl", ($scope, productImportData) ->
|
||||
$scope.entries = productImportData
|
||||
angular.module("ofn.admin").controller "ImportFeedbackCtrl", ($scope) ->
|
||||
|
||||
$scope.count = (items) ->
|
||||
total = 0
|
||||
@@ -8,4 +7,4 @@ angular.module("ofn.admin").controller "ImportFeedbackCtrl", ($scope, productImp
|
||||
total
|
||||
|
||||
$scope.attribute_invalid = (attribute, line_number) ->
|
||||
$scope.entries[line_number]['errors'][attribute] != undefined
|
||||
$scope.entries[line_number]['errors'][attribute] != undefined
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
angular.module("ofn.admin").controller "ImportFormCtrl", ($scope, $http, $filter, ProductImportService, $timeout) ->
|
||||
|
||||
$scope.entries = {}
|
||||
$scope.update_counts = {}
|
||||
$scope.reset_counts = {}
|
||||
|
||||
#$scope.import_options = {}
|
||||
|
||||
$scope.updates = {}
|
||||
$scope.updated_total = 0
|
||||
$scope.updated_ids = []
|
||||
$scope.update_errors = []
|
||||
|
||||
$scope.chunks = 0
|
||||
$scope.completed = 0
|
||||
$scope.percentage = "0%"
|
||||
$scope.started = false
|
||||
$scope.finished = false
|
||||
|
||||
$scope.countResettable = () ->
|
||||
angular.forEach $scope.supplier_product_counts, (value, key) ->
|
||||
$scope.reset_counts[key] = value
|
||||
if $scope.update_counts[key]
|
||||
$scope.reset_counts[key] -= $scope.update_counts[key]
|
||||
|
||||
$scope.resetProgress = () ->
|
||||
$scope.chunks = 0
|
||||
$scope.completed = 0
|
||||
$scope.percentage = "0%"
|
||||
$scope.started = false
|
||||
$scope.finished = false
|
||||
|
||||
$scope.step = 'import'
|
||||
|
||||
$scope.viewResults = () ->
|
||||
$scope.countResettable()
|
||||
$scope.step = 'results'
|
||||
$scope.resetProgress()
|
||||
|
||||
$scope.acceptResults = () ->
|
||||
$scope.step = 'save'
|
||||
|
||||
$scope.finalResults = () ->
|
||||
$scope.step = 'complete'
|
||||
|
||||
$scope.start = () ->
|
||||
$scope.started = true
|
||||
$scope.percentage = "1%"
|
||||
total = $scope.item_count
|
||||
size = 100
|
||||
$scope.chunks = Math.ceil(total / size)
|
||||
|
||||
i = 0
|
||||
|
||||
while i < $scope.chunks
|
||||
start = (i*size)+1
|
||||
end = (i+1)*size
|
||||
if $scope.step == 'import'
|
||||
$scope.processImport(start, end)
|
||||
if $scope.step == 'save'
|
||||
$scope.processSave(start, end)
|
||||
i++
|
||||
|
||||
$scope.processImport = (start, end) ->
|
||||
$http(
|
||||
url: $scope.import_url
|
||||
method: 'POST'
|
||||
data:
|
||||
'start': start
|
||||
'end': end
|
||||
'filepath': $scope.filepath
|
||||
'import_into': $scope.import_into
|
||||
).success((data, status, headers, config) ->
|
||||
angular.merge($scope.entries, angular.fromJson(data['entries']))
|
||||
$scope.sortUpdates(data['reset_counts'])
|
||||
|
||||
$scope.updateProgress()
|
||||
).error((data, status, headers, config) ->
|
||||
console.log('Error: '+status)
|
||||
)
|
||||
|
||||
$scope.importSettings = null
|
||||
|
||||
$scope.getSettings = () ->
|
||||
$scope.importSettings = ProductImportService.getSettings()
|
||||
|
||||
$scope.sortUpdates = (data) ->
|
||||
angular.forEach data, (value, key) ->
|
||||
if (key in $scope.update_counts)
|
||||
$scope.update_counts[key] += value['updates_count']
|
||||
else
|
||||
$scope.update_counts[key] = value['updates_count']
|
||||
|
||||
$scope.processSave = (start, end) ->
|
||||
$scope.getSettings() if $scope.importSettings == null
|
||||
$http(
|
||||
url: $scope.save_url
|
||||
method: 'POST'
|
||||
data:
|
||||
'start': start
|
||||
'end': end
|
||||
'filepath': $scope.filepath
|
||||
'import_into': $scope.import_into,
|
||||
'settings': $scope.importSettings
|
||||
).success((data, status, headers, config) ->
|
||||
$scope.sortResults(data['results'])
|
||||
|
||||
angular.forEach data['updated_ids'], (id) ->
|
||||
$scope.updated_ids.push(id)
|
||||
|
||||
angular.forEach data['errors'], (error) ->
|
||||
$scope.update_errors.push(error)
|
||||
|
||||
$scope.updateProgress()
|
||||
).error((data, status, headers, config) ->
|
||||
console.log('Error: '+status)
|
||||
)
|
||||
|
||||
$scope.sortResults = (results) ->
|
||||
angular.forEach results, (value, key) ->
|
||||
if ($scope.updates[key] != undefined)
|
||||
$scope.updates[key] += value
|
||||
else
|
||||
$scope.updates[key] = value
|
||||
|
||||
$scope.updated_total += value
|
||||
|
||||
$scope.resetAbsent = () ->
|
||||
enterprises_to_reset = []
|
||||
angular.forEach $scope.importSettings, (settings, enterprise) ->
|
||||
if settings['reset_all_absent']
|
||||
enterprises_to_reset.push(enterprise)
|
||||
|
||||
if enterprises_to_reset.length && $scope.updated_ids.length
|
||||
$http(
|
||||
url: $scope.reset_url
|
||||
method: 'POST'
|
||||
data:
|
||||
'filepath': $scope.filepath
|
||||
'import_into': $scope.import_into,
|
||||
'settings': $scope.importSettings
|
||||
'reset_absent': true,
|
||||
'updated_ids': $scope.updated_ids,
|
||||
'enterprises_to_reset': enterprises_to_reset
|
||||
).success((data, status, headers, config) ->
|
||||
console.log(data)
|
||||
$scope.updates.products_reset = data
|
||||
|
||||
).error((data, status, headers, config) ->
|
||||
console.log('Error: '+status)
|
||||
)
|
||||
|
||||
$scope.updateProgress = () ->
|
||||
$scope.completed++
|
||||
$scope.percentage = String(Math.round(($scope.completed / $scope.chunks) * 100)) + '%'
|
||||
|
||||
if $scope.completed == $scope.chunks
|
||||
$scope.finished = true
|
||||
$scope.resetAbsent() if $scope.step == 'save'
|
||||
@@ -1,12 +1,31 @@
|
||||
angular.module("ofn.admin").controller "ImportOptionsFormCtrl", ($scope, $rootScope, ProductImportService) ->
|
||||
|
||||
$scope.toggleResetAbsent = () ->
|
||||
confirmed = confirm t('js.product_import.confirmation') if $scope.resetAbsent
|
||||
$scope.initForm = () ->
|
||||
$scope.settings = {} if $scope.settings == undefined
|
||||
$scope.settings[$scope.supplierId] = {
|
||||
defaults:
|
||||
count_on_hand:
|
||||
mode: 'overwrite_all'
|
||||
on_hand:
|
||||
mode: 'overwrite_all'
|
||||
tax_category_id:
|
||||
mode: 'overwrite_all'
|
||||
shipping_category_id:
|
||||
mode: 'overwrite_all'
|
||||
available_on:
|
||||
mode: 'overwrite_all'
|
||||
}
|
||||
|
||||
if confirmed or !$scope.resetAbsent
|
||||
ProductImportService.updateResetAbsent($scope.supplierId, $scope.resetCount, $scope.resetAbsent)
|
||||
else
|
||||
$scope.resetAbsent = false
|
||||
$scope.$watch 'settings', (updated) ->
|
||||
ProductImportService.updateSettings(updated)
|
||||
, true
|
||||
|
||||
$scope.toggleResetAbsent = (id) ->
|
||||
resetAbsent = $scope.settings[id]['reset_all_absent']
|
||||
confirmed = confirm t('js.product_import.confirmation') if resetAbsent
|
||||
|
||||
if confirmed or !resetAbsent
|
||||
ProductImportService.updateResetAbsent($scope.supplierId, $scope.reset_counts[$scope.supplierId], resetAbsent)
|
||||
|
||||
$scope.resetTotal = ProductImportService.resetTotal
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ angular.module("ofn.admin").factory "ProductImportService", ($rootScope) ->
|
||||
new class ProductImportService
|
||||
suppliers: {}
|
||||
resetTotal: 0
|
||||
settings: {}
|
||||
|
||||
updateResetAbsent: (supplierId, resetCount, resetAbsent) ->
|
||||
if resetAbsent
|
||||
@@ -13,3 +14,8 @@ angular.module("ofn.admin").factory "ProductImportService", ($rootScope) ->
|
||||
|
||||
$rootScope.resetTotal = @resetTotal
|
||||
|
||||
updateSettings: (updated) ->
|
||||
angular.merge(@settings, updated)
|
||||
|
||||
getSettings: () ->
|
||||
@settings
|
||||
@@ -238,7 +238,7 @@ table.import-settings {
|
||||
}
|
||||
}
|
||||
|
||||
form.product-import, div.post-save-results {
|
||||
form.product-import, div.post-save-results, div.import-wrapper {
|
||||
input[type="submit"] {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
@@ -246,4 +246,59 @@ form.product-import, div.post-save-results {
|
||||
min-width: 8em;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
form.product-import, div.save-results {
|
||||
transition: all linear 0.25s;
|
||||
}
|
||||
|
||||
form.product-import.ng-hide, div.save-results.ng-hide {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
div.import-wrapper {
|
||||
div.progress-interface {
|
||||
text-align: center;
|
||||
transition: all linear 0.25s;
|
||||
|
||||
button {
|
||||
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
background: #ccc !important;
|
||||
}
|
||||
|
||||
}
|
||||
div.progress-interface.ng-hide {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
opacity: 0;
|
||||
}
|
||||
.post-save-results {
|
||||
a.button{
|
||||
float: left;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div.progress-bar {
|
||||
height: 25px;
|
||||
width: 30em;
|
||||
max-width: 90%;
|
||||
margin: 1em auto;
|
||||
background: #f7f7f7;
|
||||
padding: 3px;
|
||||
border-radius: 0.3em;
|
||||
border: 1px solid #eee;
|
||||
|
||||
span.progress-track{
|
||||
display: block;
|
||||
background: #b7ea53;
|
||||
height: 100%;
|
||||
border-radius: 0.3em;
|
||||
box-shadow: inset 0 0 3px rgba(0,0,0,0.3);
|
||||
transition: width 0.5s ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,13 @@ require 'roo'
|
||||
|
||||
class Admin::ProductImportController < Spree::Admin::BaseController
|
||||
|
||||
before_filter :validate_upload_presence, except: :index
|
||||
before_filter :validate_upload_presence, except: [:index, :process_data]
|
||||
|
||||
def import
|
||||
# Save uploaded file to tmp directory
|
||||
@filepath = save_uploaded_file(params[:file])
|
||||
@importer = ProductImporter.new(File.new(@filepath), spree_current_user, params[:settings])
|
||||
@original_filename = params[:file].try(:original_filename)
|
||||
@import_into = params[:settings][:import_into]
|
||||
|
||||
check_file_errors @importer
|
||||
@@ -17,10 +18,53 @@ class Admin::ProductImportController < Spree::Admin::BaseController
|
||||
@shipping_categories = Spree::ShippingCategory.order('name ASC')
|
||||
end
|
||||
|
||||
def save
|
||||
@importer = ProductImporter.new(File.new(params[:filepath]), spree_current_user, params[:settings])
|
||||
@importer.save_all if @importer.has_valid_entries?
|
||||
@import_into = params[:settings][:import_into]
|
||||
# def save
|
||||
# @importer = ProductImporter.new(File.new(params[:filepath]), spree_current_user, params[:settings])
|
||||
# @importer.save_all if @importer.has_valid_entries?
|
||||
# @import_into = params[:settings][:import_into]
|
||||
# end
|
||||
|
||||
def process_data
|
||||
@importer = ProductImporter.new(File.new(params[:filepath]), spree_current_user, {start: params[:start], end: params[:end], import_into: params[:import_into]})
|
||||
|
||||
@importer.validate_entries
|
||||
|
||||
import_results = {
|
||||
entries: @importer.entries_json,
|
||||
reset_counts: @importer.reset_counts
|
||||
}
|
||||
|
||||
render json: import_results
|
||||
end
|
||||
|
||||
def save_data
|
||||
@importer = ProductImporter.new(File.new(params[:filepath]), spree_current_user, {start: params[:start], end: params[:end], import_into: params[:import_into], settings: params[:settings]})
|
||||
|
||||
@importer.save_entries
|
||||
|
||||
save_results = {
|
||||
results: {
|
||||
products_created: @importer.products_created_count,
|
||||
products_updated: @importer.products_updated_count,
|
||||
inventory_created: @importer.inventory_created_count,
|
||||
inventory_updated: @importer.inventory_updated_count,
|
||||
products_reset: @importer.products_reset_count,
|
||||
},
|
||||
updated_ids: @importer.updated_ids,
|
||||
errors: @importer.errors.full_messages
|
||||
}
|
||||
|
||||
render json: save_results
|
||||
end
|
||||
|
||||
def reset_absent_products
|
||||
@importer = ProductImporter.new(File.new(params[:filepath]), spree_current_user, {import_into: params[:import_into], enterprises_to_reset: params[:enterprises_to_reset], updated_ids: params[:updated_ids], 'settings' => params[:settings]})
|
||||
|
||||
if params.has_key?(:enterprises_to_reset) and params.has_key?(:updated_ids)
|
||||
@importer.reset_absent(params[:updated_ids])
|
||||
end
|
||||
|
||||
render json: @importer.products_reset_count
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -58,8 +58,8 @@ module Admin
|
||||
@inventory_items = InventoryItem.where(enterprise_id: @hubs)
|
||||
|
||||
import_dates = [{id: '0', name: 'All'}]
|
||||
inventory_import_dates.map {|i| import_dates.push({id: i, name: i.to_formatted_s(:long)}) }
|
||||
@import_dates = import_dates.to_json
|
||||
inventory_import_dates.map {|i| import_dates.push({id: i.to_date, name: i.to_date.to_formatted_s(:long)}) }
|
||||
@import_dates = import_dates.uniq.to_json
|
||||
end
|
||||
|
||||
def load_collection
|
||||
|
||||
@@ -102,8 +102,8 @@ Spree::Admin::ProductsController.class_eval do
|
||||
@producers = OpenFoodNetwork::Permissions.new(spree_current_user).managed_product_enterprises.is_primary_producer.by_name
|
||||
@taxons = Spree::Taxon.order(:name)
|
||||
import_dates = [{id: '0', name: ''}]
|
||||
product_import_dates.map {|i| import_dates.push({id: i, name: i.to_formatted_s(:long)}) }
|
||||
@import_dates = import_dates.to_json
|
||||
product_import_dates.map {|i| import_dates.push({id: i.to_date, name: i.to_date.to_formatted_s(:long)}) }
|
||||
@import_dates = import_dates.uniq.to_json
|
||||
end
|
||||
|
||||
def product_import_dates
|
||||
|
||||
@@ -5,7 +5,7 @@ class ProductImporter
|
||||
include ActiveModel::Conversion
|
||||
include ActiveModel::Validations
|
||||
|
||||
attr_reader :total_supplier_products
|
||||
attr_reader :total_supplier_products, :supplier_products, :updated_ids
|
||||
|
||||
def initialize(file, current_user, import_settings={})
|
||||
if file.is_a?(File)
|
||||
@@ -33,6 +33,7 @@ class ProductImporter
|
||||
@inventory_permissions = {}
|
||||
|
||||
@total_supplier_products = 0
|
||||
@supplier_products = {}
|
||||
@reset_counts = {}
|
||||
@updated_ids = []
|
||||
|
||||
@@ -42,16 +43,6 @@ class ProductImporter
|
||||
end
|
||||
end
|
||||
|
||||
def init_permissions
|
||||
permissions = OpenFoodNetwork::Permissions.new(@current_user)
|
||||
|
||||
permissions.editable_enterprises.
|
||||
order('is_primary_producer ASC, name').
|
||||
map { |e| @editable_enterprises[e.name] = e.id }
|
||||
|
||||
@inventory_permissions = permissions.variant_override_enterprises_per_hub
|
||||
end
|
||||
|
||||
def persisted?
|
||||
false # ActiveModel
|
||||
end
|
||||
@@ -148,15 +139,55 @@ class ProductImporter
|
||||
@current_user.admin? or ( @inventory_permissions[supplier_id] and @inventory_permissions[supplier_id].include? producer_id )
|
||||
end
|
||||
|
||||
def validate_entries
|
||||
@entries.each do |entry|
|
||||
supplier_validation(entry)
|
||||
|
||||
if importing_into_inventory?
|
||||
producer_validation(entry)
|
||||
inventory_validation(entry)
|
||||
else
|
||||
category_validation(entry)
|
||||
product_validation(entry)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def save_entries
|
||||
validate_entries
|
||||
save_all_valid
|
||||
end
|
||||
|
||||
def reset_absent(updated_ids)
|
||||
@products_created = updated_ids.count
|
||||
@updated_ids = updated_ids
|
||||
reset_absent_items
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def init_product_importer
|
||||
init_permissions
|
||||
build_entries
|
||||
if @import_settings.has_key?(:start) and @import_settings.has_key?(:end)
|
||||
build_entries_in_range
|
||||
else
|
||||
build_entries
|
||||
end
|
||||
build_categories_index
|
||||
build_suppliers_index
|
||||
build_producers_index if importing_into_inventory?
|
||||
validate_all
|
||||
#validate_all
|
||||
count_existing_items unless @import_settings.has_key?(:start)
|
||||
end
|
||||
|
||||
def init_permissions
|
||||
permissions = OpenFoodNetwork::Permissions.new(@current_user)
|
||||
|
||||
permissions.editable_enterprises.
|
||||
order('is_primary_producer ASC, name').
|
||||
map { |e| @editable_enterprises[e.name] = e.id }
|
||||
|
||||
@inventory_permissions = permissions.variant_override_enterprises_per_hub
|
||||
end
|
||||
|
||||
def open_spreadsheet
|
||||
@@ -184,6 +215,21 @@ class ProductImporter
|
||||
end
|
||||
end
|
||||
|
||||
def build_entries_in_range
|
||||
start_line = @import_settings[:start]
|
||||
end_line = @import_settings[:end]
|
||||
|
||||
(start_line..end_line).each do |i|
|
||||
line_number = i + 1
|
||||
row = @sheet.row(line_number)
|
||||
row_data = Hash[[headers, row].transpose]
|
||||
entry = SpreadsheetEntry.new(row_data)
|
||||
entry.line_number = line_number
|
||||
@entries.push entry
|
||||
return if @sheet.last_row == line_number # TODO: test
|
||||
end
|
||||
end
|
||||
|
||||
def build_entries
|
||||
rows.each_with_index do |row, i|
|
||||
row_data = Hash[[headers, row].transpose]
|
||||
@@ -212,7 +258,7 @@ class ProductImporter
|
||||
end
|
||||
|
||||
def importing_into_inventory?
|
||||
@import_settings['import_into'] == 'inventories'
|
||||
@import_settings[:import_into] == 'inventories'
|
||||
end
|
||||
|
||||
def inventory_validation(entry)
|
||||
@@ -282,12 +328,7 @@ class ProductImporter
|
||||
count
|
||||
end
|
||||
|
||||
if @reset_counts[supplier_id]
|
||||
@reset_counts[supplier_id][:existing_products] = products_count
|
||||
else
|
||||
@reset_counts[supplier_id] = {existing_products: products_count}
|
||||
end
|
||||
|
||||
@supplier_products[supplier_id] = products_count
|
||||
@total_supplier_products += products_count
|
||||
end
|
||||
end
|
||||
@@ -415,7 +456,7 @@ class ProductImporter
|
||||
|
||||
self.errors.add(:importer, I18n.t(:product_importer_products_save_error)) if total_saved_count.zero?
|
||||
|
||||
reset_absent_items
|
||||
reset_absent_items unless @import_settings.has_key?(:start)
|
||||
total_saved_count
|
||||
end
|
||||
|
||||
@@ -521,10 +562,10 @@ class ProductImporter
|
||||
end
|
||||
|
||||
def reset_absent_items
|
||||
return if total_saved_count.zero? or @updated_ids.empty?
|
||||
return if total_saved_count.zero? or @updated_ids.empty? or !@import_settings.has_key?('settings')
|
||||
|
||||
enterprises_to_reset = []
|
||||
@import_settings.each do |enterprise_id, settings|
|
||||
@import_settings['settings'].each do |enterprise_id, settings|
|
||||
enterprises_to_reset.push enterprise_id if settings['reset_all_absent'] and permission_by_id?(enterprise_id)
|
||||
end
|
||||
|
||||
@@ -548,9 +589,9 @@ class ProductImporter
|
||||
end
|
||||
|
||||
def assign_defaults(object, entry)
|
||||
return unless @import_settings[entry.supplier_id.to_s] and @import_settings[entry.supplier_id.to_s]['defaults']
|
||||
return unless @import_settings.has_key?(:settings) and @import_settings[:settings][entry.supplier_id.to_s] and @import_settings[:settings][entry.supplier_id.to_s]['defaults']
|
||||
|
||||
@import_settings[entry.supplier_id.to_s]['defaults'].each do |attribute, setting|
|
||||
@import_settings[:settings][entry.supplier_id.to_s]['defaults'].each do |attribute, setting|
|
||||
next unless setting['active']
|
||||
|
||||
case setting['mode']
|
||||
|
||||
@@ -177,7 +177,7 @@ class AbilityDecorator
|
||||
can [:admin, :index, :read, :search], Spree::Taxon
|
||||
can [:admin, :index, :read, :create, :edit], Spree::Classification
|
||||
|
||||
can [:admin, :index, :import, :save], ProductImporter
|
||||
can [:admin, :index, :import, :save, :save_data, :process_data, :reset_absent_products], ProductImporter
|
||||
|
||||
# Reports page
|
||||
can [:admin, :index, :customers, :orders_and_distributors, :group_buys, :bulk_coop, :payments, :orders_and_fulfillment, :products_and_inventory, :order_cycle_management, :packing], :report
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%th #{t('admin.product_import.import.line')}
|
||||
- @importer.table_headings.each do |heading|
|
||||
%th= heading
|
||||
%tr{ng: {repeat: "(line_number, entry ) in entries | entriesFilterValid:'#{filter}' "}}
|
||||
%tr{ng: {repeat: "(line_number, entry) in (entries | entriesFilterValid:'#{entries}')"}}
|
||||
%td
|
||||
%i{ng: {class: "{'fa fa-warning warning': (count(entry.errors) > 0), 'fa fa-check-circle success': (count(entry.errors) == 0)}"}}
|
||||
%td
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
%div.import-errors{ng: {controller: 'ImportFeedbackCtrl', repeat: "(line_number, entry ) in entries | entriesFilterValid:'invalid' "}}
|
||||
%div.import-errors{ng: {controller: 'ImportFeedbackCtrl', repeat: "(line_number, entry ) in (entries | entriesFilterValid:'invalid')"}}
|
||||
%p.line
|
||||
%strong
|
||||
#{t('admin.product_import.import.item_line')} {{line_number}}:
|
||||
|
||||
@@ -3,91 +3,91 @@
|
||||
|
||||
%div{ng: {controller: 'ImportFeedbackCtrl'}}
|
||||
|
||||
%div.panel-section{ng: {controller: 'DropdownPanelsCtrl', init: "all.count = count((entries | entriesFilterValid:'all')) "}}
|
||||
%div.panel-header{ng: {click: 'togglePanel()', class: '{active: active && all.count}'}}
|
||||
%div.panel-section{ng: {controller: 'DropdownPanelsCtrl'}}
|
||||
%div.panel-header{ng: {click: 'togglePanel()', class: '{active: active && count((entries | entriesFilterValid:"all"))}'}}
|
||||
%div.header-caret
|
||||
%i{ng: {class: "{'icon-chevron-down': active, 'icon-chevron-right': !active}", hide: 'all.count == 0'}}
|
||||
%i{ng: {class: "{'icon-chevron-down': active, 'icon-chevron-right': !active}", hide: 'count((entries | entriesFilterValid:"all")) == 0'}}
|
||||
%div.header-icon.success
|
||||
%i.fa.fa-info-circle.info
|
||||
%div.header-count
|
||||
%strong.item-count
|
||||
{{all.count}}
|
||||
{{count((entries | entriesFilterValid:"all"))}}
|
||||
%div.header-description
|
||||
#{t('admin.product_import.import.entries_found')}
|
||||
%div.panel-content{ng: {hide: '!active || all.count == 0'}}
|
||||
= render 'entries_table', entries: @importer.all_entries, filter: 'all'
|
||||
%div.panel-content{ng: {hide: '!active || count((entries | entriesFilterValid:"all")) == 0'}}
|
||||
= render 'entries_table', entries: 'all'
|
||||
|
||||
%div.panel-section{ng: {controller: 'DropdownPanelsCtrl', init: "invalid.count = count((entries | entriesFilterValid:'invalid')) ", hide: 'invalid.count == 0'}}
|
||||
%div.panel-header{ng: {click: 'togglePanel()', class: '{active: active && invalid.count}'}}
|
||||
%div.panel-section{ng: {controller: 'DropdownPanelsCtrl', hide: 'count((entries | entriesFilterValid:"invalid")) == 0'}}
|
||||
%div.panel-header{ng: {click: 'togglePanel()', class: '{active: active && count((entries | entriesFilterValid:"invalid"))}'}}
|
||||
%div.header-caret
|
||||
%i{ng: {class: "{'icon-chevron-down': active, 'icon-chevron-right': !active}", hide: 'invalid.count == 0'}}
|
||||
%i{ng: {class: "{'icon-chevron-down': active, 'icon-chevron-right': !active}", hide: 'count((entries | entriesFilterValid:"invalid")) == 0'}}
|
||||
%div.header-icon.warning
|
||||
%i.fa.fa-warning
|
||||
%div.header-count
|
||||
%strong.invalid-count
|
||||
{{invalid.count}}
|
||||
{{count((entries | entriesFilterValid:"invalid"))}}
|
||||
%div.header-description
|
||||
#{t('admin.product_import.import.entries_with_errors')}
|
||||
%div.panel-content{ng: {hide: '!active || invalid.count == 0'}}
|
||||
%div.panel-content{ng: {hide: '!active || count((entries | entriesFilterValid:"invalid")) == 0'}}
|
||||
= render 'errors_list'
|
||||
%br
|
||||
= render 'entries_table', entries: @importer.all_entries, filter: 'invalid'
|
||||
= render 'entries_table', entries: 'invalid'
|
||||
|
||||
%div.panel-section{ng: {controller: 'DropdownPanelsCtrl', init: "create_product.count = count((entries | entriesFilterValid:'create_product')) ", hide: 'create_product.count == 0'}}
|
||||
%div.panel-header{ng: {click: 'togglePanel()', class: '{active: active && create_product.count}'}}
|
||||
%div.panel-section{ng: {controller: 'DropdownPanelsCtrl', hide: 'count((entries | entriesFilterValid:"create_product")) == 0'}}
|
||||
%div.panel-header{ng: {click: 'togglePanel()', class: '{active: active && count((entries | entriesFilterValid:"create_product"))}'}}
|
||||
%div.header-caret
|
||||
%i{ng: {class: "{'icon-chevron-down': active, 'icon-chevron-right': !active}", hide: 'create_product.count == 0'}}
|
||||
%i{ng: {class: "{'icon-chevron-down': active, 'icon-chevron-right': !active}", hide: 'count((entries | entriesFilterValid:"create_product")) == 0'}}
|
||||
%div.header-icon.success
|
||||
%i.fa.fa-check-circle
|
||||
%div.header-count
|
||||
%strong.create-count
|
||||
{{create_product.count}}
|
||||
{{count((entries | entriesFilterValid:"create_product"))}}
|
||||
%div.header-description
|
||||
#{t('admin.product_import.import.products_to_create')}
|
||||
%div.panel-content{ng: {hide: '!active || create_product.count == 0'}}
|
||||
= render 'entries_table', entries: @importer.all_entries, filter: 'create_product'
|
||||
%div.panel-content{ng: {hide: '!active || count((entries | entriesFilterValid:"create_product")) == 0'}}
|
||||
= render 'entries_table', entries: 'create_product'
|
||||
|
||||
%div.panel-section{ng: {controller: 'DropdownPanelsCtrl', init: "update_product.count = count((entries | entriesFilterValid:'update_product')) ", hide: 'update_product.count == 0'}}
|
||||
%div.panel-header{ng: {click: 'togglePanel()', class: '{active: active && update_product.count}'}}
|
||||
%div.panel-section{ng: {controller: 'DropdownPanelsCtrl', hide: 'count((entries | entriesFilterValid:"update_product")) == 0'}}
|
||||
%div.panel-header{ng: {click: 'togglePanel()', class: '{active: active && count((entries | entriesFilterValid:"update_product"))}'}}
|
||||
%div.header-caret
|
||||
%i{ng: {class: "{'icon-chevron-down': active, 'icon-chevron-right': !active}", hide: 'update_product.count == 0'}}
|
||||
%i{ng: {class: "{'icon-chevron-down': active, 'icon-chevron-right': !active}", hide: 'count((entries | entriesFilterValid:"update_product")) == 0'}}
|
||||
%div.header-icon.success
|
||||
%i.fa.fa-check-circle
|
||||
%div.header-count
|
||||
%strong.update-count
|
||||
{{update_product.count}}
|
||||
{{count((entries | entriesFilterValid:"update_product"))}}
|
||||
%div.header-description
|
||||
#{t('admin.product_import.import.products_to_update')}
|
||||
%div.panel-content{ng: {hide: '!active || update_product.count == 0'}}
|
||||
= render 'entries_table', entries: @importer.all_entries, filter: 'update_product'
|
||||
%div.panel-content{ng: {hide: '!active || count((entries | entriesFilterValid:"update_product")) == 0'}}
|
||||
= render 'entries_table', entries: 'update_product'
|
||||
|
||||
%div.panel-section{ng: {controller: 'DropdownPanelsCtrl', init: "create_inventory.count = count((entries | entriesFilterValid:'create_inventory')) ", hide: 'create_inventory.count == 0'}}
|
||||
%div.panel-header{ng: {click: 'togglePanel()', class: '{active: active && create_inventory.count}'}}
|
||||
%div.panel-section{ng: {controller: 'DropdownPanelsCtrl', hide: 'count((entries | entriesFilterValid:"create_inventory")) == 0'}}
|
||||
%div.panel-header{ng: {click: 'togglePanel()', class: '{active: active && count((entries | entriesFilterValid:"create_inventory"))}'}}
|
||||
%div.header-caret
|
||||
%i{ng: {class: "{'icon-chevron-down': active, 'icon-chevron-right': !active}", hide: 'create_inventory.count == 0'}}
|
||||
%i{ng: {class: "{'icon-chevron-down': active, 'icon-chevron-right': !active}", hide: 'count((entries | entriesFilterValid:"create_inventory")) == 0'}}
|
||||
%div.header-icon.success
|
||||
%i.fa.fa-check-circle
|
||||
%div.header-count
|
||||
%strong.inv-create-count
|
||||
{{create_inventory.count}}
|
||||
{{count((entries | entriesFilterValid:"create_inventory"))}}
|
||||
%div.header-description
|
||||
#{t('admin.product_import.import.inventory_to_create')}
|
||||
%div.panel-content{ng: {hide: '!active || create_inventory.count == 0'}}
|
||||
= render 'entries_table', entries: @importer.all_entries, filter: 'create_inventory'
|
||||
%div.panel-content{ng: {hide: '!active || count((entries | entriesFilterValid:"create_inventory")) == 0'}}
|
||||
= render 'entries_table', entries: 'create_inventory'
|
||||
|
||||
%div.panel-section{ng: {controller: 'DropdownPanelsCtrl', init: "update_inventory.count = count((entries | entriesFilterValid:'update_inventory')) ", hide: 'update_inventory.count == 0'}}
|
||||
%div.panel-header{ng: {click: 'togglePanel()', class: '{active: active && update_inventory.count}'}}
|
||||
%div.panel-section{ng: {controller: 'DropdownPanelsCtrl', hide: 'count((entries | entriesFilterValid:"update_inventory")) == 0'}}
|
||||
%div.panel-header{ng: {click: 'togglePanel()', class: '{active: active && count((entries | entriesFilterValid:"update_inventory"))}'}}
|
||||
%div.header-caret
|
||||
%i{ng: {class: "{'icon-chevron-down': active, 'icon-chevron-right': !active}", hide: 'update_inventory.count == 0'}}
|
||||
%i{ng: {class: "{'icon-chevron-down': active, 'icon-chevron-right': !active}", hide: 'count((entries | entriesFilterValid:"update_inventory")) == 0'}}
|
||||
%div.header-icon.success
|
||||
%i.fa.fa-check-circle
|
||||
%div.header-count
|
||||
%strong.inv-update-count
|
||||
{{update_inventory.count}}
|
||||
{{count((entries | entriesFilterValid:"update_inventory"))}}
|
||||
%div.header-description
|
||||
#{t('admin.product_import.import.inventory_to_update')}
|
||||
%div.panel-content{ng: {hide: '!active || update_inventory.count == 0'}}
|
||||
= render 'entries_table', entries: @importer.all_entries, filter: 'update_inventory'
|
||||
%div.panel-content{ng: {hide: '!active || count((entries | entriesFilterValid:"update_inventory")) == 0'}}
|
||||
= render 'entries_table', entries: 'update_inventory'
|
||||
|
||||
%div.panel-section{ng: {controller: 'ImportOptionsFormCtrl', hide: 'resetTotal == 0'}}
|
||||
%div.panel-header
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
%table.import-settings{ng: {controller: 'ImportOptionsFormCtrl', init: "supplierId = #{supplier_id}; resetCount = #{@importer.reset_counts[supplier_id][:reset_count]}"}}
|
||||
%table.import-settings{ng: {controller: 'ImportOptionsFormCtrl', init: "supplierId = #{supplier_id}; initForm()"}}
|
||||
%tr
|
||||
%td.description
|
||||
#{t('admin.product_import.import.reset_absent?')}
|
||||
%td
|
||||
= check_box_tag "settings[#{supplier_id}][reset_all_absent]", 1, false, :'ng-model' => 'resetAbsent', :'ng-change' => 'toggleResetAbsent()'
|
||||
= check_box_tag "settings[#{supplier_id}][reset_all_absent]", 1, false, 'ng-model' => "settings[#{supplier_id}]['reset_all_absent']", 'ng-change' => "toggleResetAbsent('#{supplier_id}')"
|
||||
%td
|
||||
%td
|
||||
%td
|
||||
%tr
|
||||
%td.description
|
||||
#{t('admin.product_import.import.default_stock')}
|
||||
%td
|
||||
= check_box_tag "settings[#{supplier_id}][defaults][count_on_hand][active]", 1, false, :'ng-model' => "count_on_hand_#{supplier_id}"
|
||||
= check_box_tag "settings[#{supplier_id}][defaults][count_on_hand][active]", 1, false, 'ng-model' => "settings[#{supplier_id}]['defaults']['count_on_hand']['active']"
|
||||
%td
|
||||
= select_tag "settings[#{supplier_id}][defaults][count_on_hand][mode]", options_for_select({"#{t('admin.product_import.import.overwrite_all')}" => :overwrite_all, "#{t('admin.product_import.import.overwrite_empty')}" => :overwrite_empty}), {class: 'select2 fullwidth select2-no-search', 'ng-disabled' => "!count_on_hand_#{supplier_id}"}
|
||||
%td
|
||||
= number_field_tag "settings[#{supplier_id}][defaults][count_on_hand][value]", 0, 'ng-disabled' => "!count_on_hand_#{supplier_id}"
|
||||
= select_tag "settings[#{supplier_id}][defaults][count_on_hand][mode]", options_for_select({"#{t('admin.product_import.import.overwrite_all')}" => :overwrite_all, "#{t('admin.product_import.import.overwrite_empty')}" => :overwrite_empty}), {class: 'select2 fullwidth select2-no-search', 'ng-model' => "settings[#{supplier_id}]['defaults']['count_on_hand']['mode']", 'ng-disabled' => "!settings[#{supplier_id}]['defaults']['count_on_hand']['active']"}
|
||||
%td
|
||||
= number_field_tag "settings[#{supplier_id}][defaults][count_on_hand][value]", 0, 'ng-disabled' => "!settings[#{supplier_id}]['defaults']['count_on_hand']['active']", 'ng-model' => "settings[#{supplier_id}]['defaults']['count_on_hand']['value']"
|
||||
|
||||
@@ -1,44 +1,44 @@
|
||||
%table.import-settings{ng: {controller: 'ImportOptionsFormCtrl', init: "supplierId = #{supplier_id}; resetCount = #{@importer.reset_counts[supplier_id][:reset_count]}"}}
|
||||
%table.import-settings{ng: {controller: 'ImportOptionsFormCtrl', init: "supplierId = #{supplier_id}; initForm()"}}
|
||||
%tr
|
||||
%td.description
|
||||
#{t('admin.product_import.import.reset_absent?')}
|
||||
%td
|
||||
= check_box_tag "settings[#{supplier_id}][reset_all_absent]", 1, false, :'ng-model' => 'resetAbsent', :'ng-change' => 'toggleResetAbsent()'
|
||||
= check_box_tag "settings[#{supplier_id}][reset_all_absent]", 1, false, :'ng-model' => "settings[#{supplier_id}]['reset_all_absent']", :'ng-change' => "toggleResetAbsent('#{supplier_id}')"
|
||||
%td
|
||||
%td
|
||||
%tr
|
||||
%td.description
|
||||
#{t('admin.product_import.import.default_stock')}
|
||||
%td
|
||||
= check_box_tag "settings[#{supplier_id}][defaults][on_hand][active]", 1, false, :'ng-model' => "on_hand_#{supplier_id}"
|
||||
= check_box_tag "settings[#{supplier_id}][defaults][on_hand][active]", 1, false, 'ng-model' => "settings[#{supplier_id}]['defaults']['on_hand']['active']"
|
||||
%td
|
||||
= select_tag "settings[#{supplier_id}][defaults][on_hand][mode]", options_for_select({"#{t('admin.product_import.import.overwrite_all')}" => :overwrite_all, "#{t('admin.product_import.import.overwrite_empty')}" => :overwrite_empty}), {class: 'select2 fullwidth select2-no-search', 'ng-disabled' => "!on_hand_#{supplier_id}"}
|
||||
= select_tag "settings[#{supplier_id}][defaults][on_hand][mode]", options_for_select({"#{t('admin.product_import.import.overwrite_all')}" => :overwrite_all, "#{t('admin.product_import.import.overwrite_empty')}" => :overwrite_empty}), {class: 'select2 fullwidth select2-no-search', 'ng-model' => "settings[#{supplier_id}]['defaults']['on_hand']['mode']", 'ng-disabled' => "!settings[#{supplier_id}]['defaults']['on_hand']['active']"}
|
||||
%td
|
||||
= number_field_tag "settings[#{supplier_id}][defaults][on_hand][value]", 0, 'ng-disabled' => "!on_hand_#{supplier_id}"
|
||||
= number_field_tag "settings[#{supplier_id}][defaults][on_hand][value]", 0, 'ng-model' => "settings[#{supplier_id}]['defaults']['on_hand']['value']", 'ng-disabled' => "!settings[#{supplier_id}]['defaults']['on_hand']['active']"
|
||||
%tr
|
||||
%td.description
|
||||
#{t('admin.product_import.import.default_tax_cat')}
|
||||
%td
|
||||
= check_box_tag "settings[#{supplier_id}][defaults][tax_category_id][active]", 1, false, :'ng-model' => "tax_category_id_#{supplier_id}"
|
||||
= check_box_tag "settings[#{supplier_id}][defaults][tax_category_id][active]", 1, false, 'ng-model' => "settings[#{supplier_id}]['defaults']['tax_category_id']['active']"
|
||||
%td
|
||||
= select_tag "settings[#{supplier_id}][defaults][tax_category_id][mode]", options_for_select({"#{t('admin.product_import.import.overwrite_all')}" => :overwrite_all, "#{t('admin.product_import.import.overwrite_empty')}" => :overwrite_empty}), {class: 'select2 fullwidth select2-no-search', 'ng-disabled' => "!tax_category_id_#{supplier_id}"}
|
||||
= select_tag "settings[#{supplier_id}][defaults][tax_category_id][mode]", options_for_select({"#{t('admin.product_import.import.overwrite_all')}" => :overwrite_all, "#{t('admin.product_import.import.overwrite_empty')}" => :overwrite_empty}), {class: 'select2 fullwidth select2-no-search', 'ng-model' => "settings[#{supplier_id}]['defaults']['tax_category_id']['mode']", 'ng-disabled' => "!settings[#{supplier_id}]['defaults']['tax_category_id']['active']"}
|
||||
%td
|
||||
= select_tag "settings[#{supplier_id}][defaults][tax_category_id][value]", options_for_select(@tax_categories.map {|tc| [tc.name, tc.id]}), {prompt: 'None', class: 'select2 fullwidth select2-no-search', 'ng-disabled' => "!tax_category_id_#{supplier_id}"}
|
||||
= select_tag "settings[#{supplier_id}][defaults][tax_category_id][value]", options_for_select(@tax_categories.map {|tc| [tc.name, tc.id]}), {prompt: 'None', class: 'select2 fullwidth select2-no-search', 'ng-model' => "settings[#{supplier_id}]['defaults']['tax_category_id']['value']", 'ng-disabled' => "!settings[#{supplier_id}]['defaults']['tax_category_id']['active']"}
|
||||
%tr
|
||||
%td.description
|
||||
#{t('admin.product_import.import.default_shipping_cat')}
|
||||
%td
|
||||
= check_box_tag "settings[#{supplier_id}][defaults][shipping_category_id][active]", 1, false, :'ng-model' => "shipping_category_id_#{supplier_id}"
|
||||
= check_box_tag "settings[#{supplier_id}][defaults][shipping_category_id][active]", 1, false, 'ng-model' => "settings[#{supplier_id}]['defaults']['shipping_category_id']['active']"
|
||||
%td
|
||||
= select_tag "settings[#{supplier_id}][defaults][shipping_category_id][mode]", options_for_select({"#{t('admin.product_import.import.overwrite_all')}" => :overwrite_all, "#{t('admin.product_import.import.overwrite_empty')}" => :overwrite_empty}), {class: 'select2 fullwidth select2-no-search', 'ng-disabled' => "!shipping_category_id_#{supplier_id}"}
|
||||
= select_tag "settings[#{supplier_id}][defaults][shipping_category_id][mode]", options_for_select({"#{t('admin.product_import.import.overwrite_all')}" => :overwrite_all, "#{t('admin.product_import.import.overwrite_empty')}" => :overwrite_empty}), {class: 'select2 fullwidth select2-no-search', 'ng-model' => "settings[#{supplier_id}]['defaults']['shipping_category_id']['mode']", 'ng-disabled' => "!settings[#{supplier_id}]['defaults']['shipping_category_id']['active']"}
|
||||
%td
|
||||
= select_tag "settings[#{supplier_id}][defaults][shipping_category_id][value]", options_for_select(@shipping_categories.map {|sc| [sc.name, sc.id]}), {prompt: 'None', class: 'select2 fullwidth select2-no-search', 'ng-disabled' => "!shipping_category_id_#{supplier_id}"}
|
||||
= select_tag "settings[#{supplier_id}][defaults][shipping_category_id][value]", options_for_select(@shipping_categories.map {|sc| [sc.name, sc.id]}), {prompt: 'None', class: 'select2 fullwidth select2-no-search', 'ng-model' => "settings[#{supplier_id}]['defaults']['shipping_category_id']['value']", 'ng-disabled' => "!settings[#{supplier_id}]['defaults']['shipping_category_id']['active']"}
|
||||
%tr
|
||||
%td.description
|
||||
#{t('admin.product_import.import.default_available_date')}
|
||||
%td
|
||||
= check_box_tag "settings[#{supplier_id}][defaults][available_on][active]", 1, false, :'ng-model' => "available_on_#{supplier_id}"
|
||||
= check_box_tag "settings[#{supplier_id}][defaults][available_on][active]", 1, false, 'ng-model' => "settings[#{supplier_id}]['defaults']['available_on']['active']"
|
||||
%td
|
||||
= select_tag "settings[#{supplier_id}][defaults][available_on][mode]", options_for_select({"#{t('admin.product_import.import.overwrite_all')}" => :overwrite_all, "#{t('admin.product_import.import.overwrite_empty')}" => :overwrite_empty}), {class: 'select2 fullwidth select2-no-search', 'ng-disabled' => "!available_on_#{supplier_id}"}
|
||||
= select_tag "settings[#{supplier_id}][defaults][available_on][mode]", options_for_select({"#{t('admin.product_import.import.overwrite_all')}" => :overwrite_all, "#{t('admin.product_import.import.overwrite_empty')}" => :overwrite_empty}), {class: 'select2 fullwidth select2-no-search', 'ng-model' => "settings[#{supplier_id}]['defaults']['available_on']['mode']", 'ng-disabled' => "!settings[#{supplier_id}]['defaults']['available_on']['active']"}
|
||||
%td
|
||||
= text_field_tag "settings[#{supplier_id}][defaults][available_on][value]", nil, {class: 'datepicker', placeholder: 'Today', 'ng-disabled' => "!available_on_#{supplier_id}"}
|
||||
= text_field_tag "settings[#{supplier_id}][defaults][available_on][value]", nil, {class: 'datepicker', placeholder: 'Today', 'ng-model' => "settings[#{supplier_id}]['defaults']['available_on']['value']", 'ng-disabled' => "!settings[#{supplier_id}]['defaults']['available_on']['active']"}
|
||||
|
||||
60
app/views/admin/product_import/_save_results.html.haml
Normal file
60
app/views/admin/product_import/_save_results.html.haml
Normal file
@@ -0,0 +1,60 @@
|
||||
|
||||
%h5 #{t('admin.product_import.save.final_results')}
|
||||
%br
|
||||
|
||||
%div.post-save-results
|
||||
|
||||
%p{ng: {show: 'updates.products_created'}}
|
||||
%i.fa{ng: {class: "{'fa-info-circle': updates.products_created == 0, 'fa-check-circle': updates.products_created > 0}"}}
|
||||
%strong.created-count
|
||||
{{updates.products_created}}
|
||||
#{t('admin.product_import.save.products_created')}
|
||||
|
||||
%p{ng: {show: 'updates.products_updated'}}
|
||||
%i.fa{ng: {class: "{'fa-info-circle': updates.products_updated == 0, 'fa-check-circle': updates.products_updated > 0}"}}
|
||||
%strong.updated-count
|
||||
{{updates.products_updated}}
|
||||
#{t('admin.product_import.save.products_updated')}
|
||||
|
||||
%p{ng: {show: 'updates.inventory_created'}}
|
||||
%i.fa{ng: {class: "{'fa-info-circle': updates.inventory_created == 0, 'fa-check-circle': updates.inventory_created > 0}"}}
|
||||
%strong.inv-created-count
|
||||
{{updates.inventory_created}}
|
||||
#{t('admin.product_import.save.inventory_created')}
|
||||
|
||||
%p{ng: {show: 'updates.inventory_updated'}}
|
||||
%i.fa{ng: {class: "{'fa-info-circle': updates.inventory_updated == 0, 'fa-check-circle': updates.inventory_updated > 0}"}}
|
||||
%strong.inv-updated-count
|
||||
{{updates.inventory_updated}}
|
||||
#{t('admin.product_import.save.inventory_updated')}
|
||||
|
||||
%p{ng: {show: 'updates.products_reset'}}
|
||||
%i.fa.fa-info-circle
|
||||
%strong.reset-count
|
||||
{{updates.products_reset}}
|
||||
- if @import_into == 'inventories'
|
||||
#{t('admin.product_import.save.inventory_reset')}
|
||||
- else
|
||||
#{t('admin.product_import.save.products_reset')}
|
||||
|
||||
%br
|
||||
|
||||
%p{ng: {show: 'update_errors.length == 0'}}
|
||||
#{t('admin.product_import.save.all_saved')}
|
||||
|
||||
%div{ng: {show: 'update_errors.length > 0'}}
|
||||
%p {{updated_total}} #{t('admin.product_import.save.some_saved')}
|
||||
%br
|
||||
%h5 #{t('admin.product_import.save.save_errors')}
|
||||
|
||||
%p.save-error{ng: {repeat: 'error in update_errors'}}
|
||||
- {{error}}
|
||||
|
||||
%br
|
||||
%div{ng: {show: 'updated_total > 0'}}
|
||||
- if @import_into == 'inventories'
|
||||
%a.button.view{href: main_app.admin_inventory_path} #{t('admin.product_import.save.view_inventory')}
|
||||
- else
|
||||
%a.button.view{href: bulk_edit_admin_products_path + '?latest_import=true'} #{t('admin.product_import.save.view_products')}
|
||||
|
||||
%a.button{href: main_app.admin_product_import_path} #{t('admin.back')}
|
||||
@@ -14,4 +14,4 @@
|
||||
%br
|
||||
%br
|
||||
%br
|
||||
= submit_tag "#{t('admin.product_import.index.import')}"
|
||||
= submit_tag "#{t('admin.product_import.index.upload')}"
|
||||
|
||||
@@ -3,36 +3,62 @@
|
||||
|
||||
= render partial: 'spree/admin/shared/product_sub_menu'
|
||||
|
||||
= form_tag main_app.admin_product_import_save_path, {class: 'product-import', 'ng-app' => 'ofn.admin'} do
|
||||
.import-wrapper{ng: {app: 'ofn.admin', controller: 'ImportFormCtrl', init: "supplier_product_counts = #{@importer.supplier_products.to_json}"}}
|
||||
|
||||
- if !@importer.has_valid_entries? #and @importer.invalid_count
|
||||
- if @importer.item_count == 0 #and @importer.invalid_count
|
||||
%h5 #{t('admin.product_import.import.no_valid_entries')}
|
||||
%p #{t('admin.product_import.import.none_to_save')}
|
||||
%br
|
||||
- else
|
||||
.progress-interface{ng: {show: 'step == "import"'}}
|
||||
%span.filename
|
||||
#{@original_filename}
|
||||
%span.percentage
|
||||
({{percentage}})
|
||||
.progress-bar
|
||||
%span.progress-track{class: 'ng-binding', style: "width:{{percentage}}"}
|
||||
%button.start_import{ng: {click: 'start()', disabled: 'started', init: "item_count = #{@importer.item_count}; import_url = '#{main_app.admin_product_import_process_async_path}'; filepath = '#{@filepath}'; import_into = '#{@import_into}'"}}
|
||||
#{t('admin.product_import.index.import')}
|
||||
%button.review{ng: {click: 'viewResults()', disabled: '!finished'}}
|
||||
#{t('admin.product_import.import.review')}
|
||||
|
||||
= render partial: "admin/json/injection_ams", locals: {ngModule: 'ofn.admin', name: 'productImportData', json: @importer.entries_json}
|
||||
= form_tag false, {class: 'product-import', name: 'importForm', 'ng-show' => 'step == "results"'} do
|
||||
|
||||
= render 'import_options' if @importer.has_valid_entries?
|
||||
= render 'import_options' if @importer.table_headings
|
||||
|
||||
= render 'import_review' if @importer.has_entries?
|
||||
= render 'import_review' if @importer.table_headings
|
||||
|
||||
%div{ng: {controller: 'ImportFeedbackCtrl', show: "count((entries | entriesFilterValid:'valid')) > 0"}}
|
||||
%div{ng: {if: "count((entries | entriesFilterValid:'invalid')) > 0"}}
|
||||
%div{ng: {controller: 'ImportFeedbackCtrl', show: 'count((entries | entriesFilterValid:"valid")) > 0'}}
|
||||
%div{ng: {if: 'count((entries | entriesFilterValid:"invalid")) > 0'}}
|
||||
%br
|
||||
%h5 #{t('admin.product_import.import.some_invalid_entries')}
|
||||
%p #{t('admin.product_import.import.save_valid?')}
|
||||
%div{ng: {show: 'count((entries | entriesFilterValid:"invalid")) == 0'}}
|
||||
%br
|
||||
%h5 #{t('admin.product_import.import.no_errors')}
|
||||
%p #{t('admin.product_import.import.save_all_imported?')}
|
||||
%br
|
||||
%h5 #{t('admin.product_import.import.some_invalid_entries')}
|
||||
%p #{t('admin.product_import.import.save_valid?')}
|
||||
%div{ng: {show: "count((entries | entriesFilterValid:'invalid')) == 0"}}
|
||||
= hidden_field_tag :filepath, @filepath
|
||||
= hidden_field_tag "settings[import_into]", @import_into
|
||||
|
||||
%a.button{href: '', ng: {click: 'acceptResults()'}}
|
||||
#{t('admin.product_import.import.proceed')}
|
||||
|
||||
%a.button{href: main_app.admin_product_import_path} #{t('admin.cancel')}
|
||||
|
||||
%div{ng: {controller: 'ImportFeedbackCtrl', show: 'count((entries | entriesFilterValid:"valid")) == 0'}}
|
||||
%br
|
||||
%h5 #{t('admin.product_import.import.no_errors')}
|
||||
%p #{t('admin.product_import.import.save_all_imported?')}
|
||||
%br
|
||||
= hidden_field_tag :filepath, @filepath
|
||||
= hidden_field_tag "settings[import_into]", @import_into
|
||||
= submit_tag t('admin.save')
|
||||
%a.button{href: main_app.admin_product_import_path} #{t('admin.cancel')}
|
||||
|
||||
%div{ng: {controller: 'ImportFeedbackCtrl', show: "count((entries | entriesFilterValid:'valid')) == 0"}}
|
||||
%br
|
||||
%a.button{href: main_app.admin_product_import_path} #{t('admin.cancel')}
|
||||
%a.button{href: main_app.admin_product_import_path} #{t('admin.cancel')}
|
||||
|
||||
.progress-interface{ng: {show: 'step == "save"'}}
|
||||
%span.filename
|
||||
#{t('admin.product_import.import.save_imported')} ({{percentage}})
|
||||
.progress-bar{}
|
||||
%span.progress-track{ng: {style: "{'width':percentage}"}}
|
||||
%button.start_save{ng: {click: 'start()', disabled: 'started', init: "item_count = #{@importer.item_count}; save_url = '#{main_app.admin_product_import_save_async_path}'; reset_url = '#{main_app.admin_product_import_reset_async_path}'; filepath = '#{@filepath}'; import_into = '#{@import_into}'"}}
|
||||
#{t('admin.product_import.import.save')}
|
||||
%button.view_results{ng: {click: 'finalResults()', disabled: '!finished'}}
|
||||
#{t('admin.product_import.import.results')}
|
||||
|
||||
.save-results{ng: {show: 'step == "complete"'}}
|
||||
= render 'save_results'
|
||||
|
||||
@@ -500,7 +500,13 @@ en:
|
||||
product_list: Product list
|
||||
inventories: Inventories
|
||||
import: Import
|
||||
upload: Upload
|
||||
import:
|
||||
review: Review
|
||||
proceed: Proceed
|
||||
save: Save
|
||||
results: Results
|
||||
save_imported: Save imported products
|
||||
no_valid_entries: No valid entries found
|
||||
none_to_save: There are no entries that can be saved
|
||||
some_invalid_entries: Imported file contains some invalid entries
|
||||
@@ -538,8 +544,8 @@ en:
|
||||
inventory_updated: Inventory items updated
|
||||
products_reset: Products had stock level reset to zero
|
||||
inventory_reset: Inventory items had stock level reset to zero
|
||||
all_saved: "All %{num} items saved successfully"
|
||||
total_saved: "%{num} items saved successfully"
|
||||
all_saved: "All items saved successfully"
|
||||
some_saved: "items saved successfully"
|
||||
save_errors: Save errors
|
||||
view_products: View Products
|
||||
view_inventory: View Inventory
|
||||
|
||||
@@ -135,7 +135,10 @@ Openfoodnetwork::Application.routes.draw do
|
||||
|
||||
get '/product_import', to: 'product_import#index'
|
||||
post '/product_import', to: 'product_import#import'
|
||||
post '/product_import/save', to: 'product_import#save', as: 'product_import_save'
|
||||
post '/product_import/process_data', to: 'product_import#process_data', as: 'product_import_process_async'
|
||||
post '/product_import/save_data', to: 'product_import#save_data', as: 'product_import_save_async'
|
||||
post '/product_import/reset_absent', to: 'product_import#reset_absent_products', as: 'product_import_reset_async'
|
||||
#post '/product_import/save', to: 'product_import#save', as: 'product_import_save'
|
||||
|
||||
resources :variant_overrides do
|
||||
post :bulk_update, on: :collection
|
||||
|
||||
@@ -43,14 +43,31 @@ feature "Product Import", js: true do
|
||||
|
||||
expect(page).to have_content "Select a spreadsheet to upload"
|
||||
attach_file 'file', '/tmp/test.csv'
|
||||
click_button 'Upload'
|
||||
|
||||
expect(page).to have_selector 'button.start_import'
|
||||
expect(page).to have_selector "button.review[disabled='disabled']"
|
||||
|
||||
click_button 'Import'
|
||||
wait_until { page.find("button.review:not([disabled='disabled'])").present? }
|
||||
click_button 'Review'
|
||||
|
||||
expect(page).to have_selector '.item-count', text: "2"
|
||||
expect(page).to_not have_selector '.invalid-count'
|
||||
expect(page).to have_selector '.create-count', text: "2"
|
||||
expect(page).to_not have_selector '.update-count'
|
||||
|
||||
click_link 'Proceed'
|
||||
|
||||
expect(page).to have_selector 'button.start_save'
|
||||
expect(page).to have_selector "button.view_results[disabled='disabled']"
|
||||
|
||||
sleep 0.5
|
||||
click_button 'Save'
|
||||
wait_until { page.find("button.view_results:not([disabled='disabled'])").present? }
|
||||
|
||||
click_button 'Results'
|
||||
|
||||
expect(page).to have_selector '.created-count', text: '2'
|
||||
expect(page).to_not have_selector '.updated-count'
|
||||
|
||||
@@ -61,6 +78,8 @@ feature "Product Import", js: true do
|
||||
potatoes.price.should == 6.50
|
||||
potatoes.variants.first.import_date.should be_within(1.minute).of DateTime.now
|
||||
|
||||
wait_until { page.find("a.button.view").present? }
|
||||
|
||||
click_link 'View Products'
|
||||
|
||||
expect(page).to have_content 'Bulk Edit Products'
|
||||
@@ -69,36 +88,6 @@ feature "Product Import", js: true do
|
||||
expect(page).to have_field "product_name", with: potatoes.name
|
||||
end
|
||||
|
||||
it "displays info about invalid entries but still allows saving of valid entries" do
|
||||
csv_data = CSV.generate do |csv|
|
||||
csv << ["name", "supplier", "category", "on_hand", "price", "unit_value", "variant_unit", "variant_unit_scale"]
|
||||
csv << ["Good Carrots", "User Enterprise", "Vegetables", "5", "3.20", "500", "weight", "1"]
|
||||
csv << ["Bad Potatoes", "", "Vegetables", "6", "6.50", "1000", "", "1000"]
|
||||
end
|
||||
File.write('/tmp/test.csv', csv_data)
|
||||
|
||||
visit main_app.admin_product_import_path
|
||||
|
||||
expect(page).to have_content "Select a spreadsheet to upload"
|
||||
attach_file('file', '/tmp/test.csv')
|
||||
click_button 'Import'
|
||||
|
||||
expect(page).to have_selector '.item-count', text: "2"
|
||||
expect(page).to have_selector '.invalid-count', text: "1"
|
||||
expect(page).to have_selector '.create-count', text: "1"
|
||||
|
||||
expect(page).to have_selector 'input[type=submit][value="Save"]'
|
||||
click_button 'Save'
|
||||
|
||||
expect(page).to have_selector '.created-count', text: '1'
|
||||
|
||||
Spree::Product.find_by_name('Bad Potatoes').should == nil
|
||||
carrots = Spree::Product.find_by_name('Good Carrots')
|
||||
carrots.supplier.should == enterprise
|
||||
carrots.on_hand.should == 5
|
||||
carrots.price.should == 3.20
|
||||
end
|
||||
|
||||
it "displays info about invalid entries but no save button if all items are invalid" do
|
||||
csv_data = CSV.generate do |csv|
|
||||
csv << ["name", "supplier", "category", "on_hand", "price", "unit_value", "variant_unit", "variant_unit_scale"]
|
||||
@@ -111,7 +100,11 @@ feature "Product Import", js: true do
|
||||
|
||||
expect(page).to have_content "Select a spreadsheet to upload"
|
||||
attach_file 'file', '/tmp/test.csv'
|
||||
click_button 'Upload'
|
||||
|
||||
click_button 'Import'
|
||||
wait_until { page.find("button.review:not([disabled='disabled'])").present? }
|
||||
click_button 'Review'
|
||||
|
||||
expect(page).to have_selector '.item-count', text: "2"
|
||||
expect(page).to have_selector '.invalid-count', text: "2"
|
||||
@@ -121,75 +114,11 @@ feature "Product Import", js: true do
|
||||
expect(page).to_not have_selector 'input[type=submit][value="Save"]'
|
||||
end
|
||||
|
||||
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", "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)
|
||||
|
||||
visit main_app.admin_product_import_path
|
||||
attach_file 'file', '/tmp/test.csv'
|
||||
click_button 'Import'
|
||||
|
||||
expect(page).to have_selector '.item-count', text: "2"
|
||||
expect(page).to_not have_selector '.invalid-count'
|
||||
expect(page).to have_selector '.create-count', text: "1"
|
||||
expect(page).to have_selector '.update-count', text: "1"
|
||||
|
||||
click_button 'Save'
|
||||
|
||||
expect(page).to have_selector '.created-count', text: '1'
|
||||
expect(page).to have_selector '.updated-count', text: '1'
|
||||
|
||||
added_coffee = Spree::Variant.find_by_display_name('Emergent Coffee')
|
||||
added_coffee.product.name.should == 'Hypothetical Cake'
|
||||
added_coffee.price.should == 3.50
|
||||
added_coffee.on_hand.should == 6
|
||||
added_coffee.import_date.should be_within(1.minute).of DateTime.now
|
||||
|
||||
updated_banana = Spree::Variant.find_by_display_name('Preexisting Banana')
|
||||
updated_banana.product.name.should == 'Hypothetical Cake'
|
||||
updated_banana.price.should == 5.50
|
||||
updated_banana.on_hand.should == 5
|
||||
updated_banana.import_date.should be_within(1.minute).of DateTime.now
|
||||
end
|
||||
|
||||
it "can add a new product and sub-variants of that product at the same time" do
|
||||
csv_data = CSV.generate do |csv|
|
||||
csv << ["name", "supplier", "category", "on_hand", "price", "unit_value", "variant_unit", "variant_unit_scale", "display_name"]
|
||||
csv << ["Potatoes", "User Enterprise", "Vegetables", "5", "3.50", "500", "weight", "1000", "Small Bag"]
|
||||
csv << ["Potatoes", "User Enterprise", "Vegetables", "6", "5.50", "2000", "weight", "1000", "Big Bag"]
|
||||
end
|
||||
File.write('/tmp/test.csv', csv_data)
|
||||
|
||||
visit main_app.admin_product_import_path
|
||||
attach_file 'file', '/tmp/test.csv'
|
||||
click_button 'Import'
|
||||
|
||||
expect(page).to have_selector '.item-count', text: "2"
|
||||
expect(page).to_not have_selector '.invalid-count'
|
||||
expect(page).to have_selector '.create-count', text: "2"
|
||||
|
||||
click_button 'Save'
|
||||
expect(page).to have_selector '.created-count', text: '2'
|
||||
|
||||
small_bag = Spree::Variant.find_by_display_name('Small Bag')
|
||||
small_bag.product.name.should == 'Potatoes'
|
||||
small_bag.price.should == 3.50
|
||||
small_bag.on_hand.should == 5
|
||||
|
||||
big_bag = Spree::Variant.find_by_display_name('Big Bag')
|
||||
big_bag.product.name.should == 'Potatoes'
|
||||
big_bag.price.should == 5.50
|
||||
big_bag.on_hand.should == 6
|
||||
end
|
||||
|
||||
it "records a timestamp on import that can be viewed and filtered under Bulk Edit Products" 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)
|
||||
|
||||
@@ -197,17 +126,29 @@ feature "Product Import", js: true do
|
||||
|
||||
expect(page).to have_content "Select a spreadsheet to upload"
|
||||
attach_file 'file', '/tmp/test.csv'
|
||||
click_button 'Upload'
|
||||
|
||||
click_button 'Import'
|
||||
wait_until { page.find("button.review:not([disabled='disabled'])").present? }
|
||||
click_button 'Review'
|
||||
|
||||
click_link 'Proceed'
|
||||
sleep 0.5
|
||||
click_button 'Save'
|
||||
wait_until { page.find("button.view_results:not([disabled='disabled'])").present? }
|
||||
click_button 'Results'
|
||||
|
||||
carrots = Spree::Product.find_by_name('Carrots')
|
||||
carrots.variants.first.import_date.should be_within(1.minute).of DateTime.now
|
||||
potatoes = Spree::Product.find_by_name('Potatoes')
|
||||
potatoes.variants.first.import_date.should be_within(1.minute).of DateTime.now
|
||||
|
||||
click_link 'View Products'
|
||||
|
||||
wait_until { page.find("#p_#{carrots.id}").present? }
|
||||
|
||||
expect(page).to have_field "product_name", with: carrots.name
|
||||
expect(page).to have_field "product_name", with: potatoes.name
|
||||
find("div#columns-dropdown", :text => "COLUMNS").click
|
||||
find("div#columns-dropdown div.menu div.menu_item", text: "Import").click
|
||||
find("div#columns-dropdown", :text => "COLUMNS").click
|
||||
@@ -217,10 +158,11 @@ feature "Product Import", js: true do
|
||||
end
|
||||
|
||||
expect(page).to have_selector 'div#s2id_import_date_filter'
|
||||
import_time = carrots.import_date.to_formatted_s(:long)
|
||||
import_time = carrots.import_date.to_date.to_formatted_s(:long)
|
||||
select import_time, from: "import_date_filter", visible: false
|
||||
|
||||
expect(page).to have_field "product_name", with: carrots.name
|
||||
expect(page).to have_field "product_name", with: potatoes.name
|
||||
expect(page).to_not have_field "product_name", with: product.name
|
||||
expect(page).to_not have_field "product_name", with: product2.name
|
||||
end
|
||||
@@ -238,7 +180,11 @@ feature "Product Import", js: true do
|
||||
|
||||
attach_file 'file', '/tmp/test.csv'
|
||||
select 'Inventories', from: "settings_import_into", visible: false
|
||||
click_button 'Upload'
|
||||
|
||||
click_button 'Import'
|
||||
wait_until { page.find("button.review:not([disabled='disabled'])").present? }
|
||||
click_button 'Review'
|
||||
|
||||
expect(page).to have_selector '.item-count', text: "3"
|
||||
expect(page).to_not have_selector '.invalid-count'
|
||||
@@ -247,7 +193,11 @@ feature "Product Import", js: true do
|
||||
expect(page).to have_selector '.inv-create-count', text: "2"
|
||||
expect(page).to have_selector '.inv-update-count', text: "1"
|
||||
|
||||
click_link 'Proceed'
|
||||
sleep 0.5
|
||||
click_button 'Save'
|
||||
wait_until { page.find("button.view_results:not([disabled='disabled'])").present? }
|
||||
click_button 'Results'
|
||||
|
||||
expect(page).to_not have_selector '.created-count'
|
||||
expect(page).to_not have_selector '.updated-count'
|
||||
@@ -288,7 +238,7 @@ feature "Product Import", js: true do
|
||||
|
||||
visit main_app.admin_product_import_path
|
||||
attach_file 'file', '/tmp/test.txt'
|
||||
click_button 'Import'
|
||||
click_button 'Upload'
|
||||
|
||||
expect(page).to have_content "Importer could not process file: invalid filetype"
|
||||
expect(page).to_not have_selector 'input[type=submit][value="Save"]'
|
||||
@@ -296,10 +246,9 @@ feature "Product Import", js: true do
|
||||
File.delete('/tmp/test.txt')
|
||||
end
|
||||
|
||||
it "returns and error if nothing was uploaded" do
|
||||
it "returns an error if nothing was uploaded" do
|
||||
visit main_app.admin_product_import_path
|
||||
expect(page).to have_content 'Select a spreadsheet to upload'
|
||||
click_button 'Import'
|
||||
click_button 'Upload'
|
||||
|
||||
expect(flash_message).to eq I18n.t(:product_import_file_not_found_notice)
|
||||
end
|
||||
@@ -309,7 +258,7 @@ feature "Product Import", js: true do
|
||||
|
||||
visit main_app.admin_product_import_path
|
||||
attach_file 'file', '/tmp/test.csv'
|
||||
click_button 'Import'
|
||||
click_button 'Upload'
|
||||
|
||||
expect(page).to_not have_selector '.create-count'
|
||||
expect(page).to_not have_selector '.update-count'
|
||||
@@ -333,7 +282,11 @@ feature "Product Import", js: true do
|
||||
visit main_app.admin_product_import_path
|
||||
|
||||
attach_file 'file', '/tmp/test.csv'
|
||||
click_button 'Upload'
|
||||
|
||||
click_button 'Import'
|
||||
wait_until { page.find("button.review:not([disabled='disabled'])").present? }
|
||||
click_button 'Review'
|
||||
|
||||
expect(page).to have_selector '.item-count', text: "2"
|
||||
expect(page).to have_selector '.invalid-count', text: "1"
|
||||
@@ -341,249 +294,16 @@ feature "Product Import", js: true do
|
||||
|
||||
expect(page.body).to have_content 'you do not have permission'
|
||||
|
||||
click_link 'Proceed'
|
||||
sleep 0.5
|
||||
click_button 'Save'
|
||||
wait_until { page.find("button.view_results:not([disabled='disabled'])").present? }
|
||||
click_button 'Results'
|
||||
|
||||
expect(page).to have_selector '.created-count', text: '1'
|
||||
|
||||
Spree::Product.find_by_name('My Carrots').should be_a Spree::Product
|
||||
Spree::Product.find_by_name('Your Potatoes').should == nil
|
||||
end
|
||||
|
||||
it "allows creating inventories for producers that a user's hub has permission for" do
|
||||
csv_data = CSV.generate do |csv|
|
||||
csv << ["name", "producer", "supplier", "category", "on_hand", "price", "unit_value"]
|
||||
csv << ["Beans", "User Enterprise", "Another Enterprise", "Vegetables", "777", "3.20", "500"]
|
||||
end
|
||||
File.write('/tmp/test.csv', csv_data)
|
||||
|
||||
quick_login_as user2
|
||||
visit main_app.admin_product_import_path
|
||||
|
||||
attach_file 'file', '/tmp/test.csv'
|
||||
select 'Inventories', from: "settings_import_into", visible: false
|
||||
click_button 'Import'
|
||||
|
||||
expect(page).to have_selector '.item-count', text: "1"
|
||||
expect(page).to_not have_selector '.invalid-count'
|
||||
expect(page).to have_selector '.inv-create-count', text: "1"
|
||||
|
||||
click_button 'Save'
|
||||
|
||||
expect(page).to have_selector '.inv-created-count', text: '1'
|
||||
|
||||
beans = VariantOverride.where(variant_id: product2.variants.first.id, hub_id: enterprise2.id).first
|
||||
beans.count_on_hand.should == 777
|
||||
end
|
||||
|
||||
it "does not allow creating inventories for producers that a user's hubs don't have permission for" do
|
||||
csv_data = CSV.generate do |csv|
|
||||
csv << ["name", "supplier", "category", "on_hand", "price", "unit_value"]
|
||||
csv << ["Beans", "User Enterprise", "Vegetables", "5", "3.20", "500"]
|
||||
csv << ["Sprouts", "User Enterprise", "Vegetables", "6", "6.50", "500"]
|
||||
end
|
||||
File.write('/tmp/test.csv', csv_data)
|
||||
|
||||
quick_login_as user2
|
||||
visit main_app.admin_product_import_path
|
||||
|
||||
attach_file 'file', '/tmp/test.csv'
|
||||
select 'Inventories', from: "settings_import_into", visible: false
|
||||
click_button 'Import'
|
||||
|
||||
expect(page).to have_selector '.item-count', text: "2"
|
||||
expect(page).to have_selector '.invalid-count', text: "2"
|
||||
expect(page).to_not have_selector '.inv-create-count'
|
||||
|
||||
expect(page.body).to have_content 'you do not have permission'
|
||||
expect(page).to_not have_selector 'input[type=submit][value="Save"]'
|
||||
end
|
||||
end
|
||||
|
||||
describe "applying settings and defaults on import" do
|
||||
before { quick_login_as_admin }
|
||||
after { File.delete('/tmp/test.csv') }
|
||||
|
||||
it "can reset 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 << ["Beans", "User Enterprise", "Vegetables", "6", "6.50", "500", "weight", "1"]
|
||||
end
|
||||
File.write('/tmp/test.csv', csv_data)
|
||||
|
||||
visit main_app.admin_product_import_path
|
||||
|
||||
attach_file 'file', '/tmp/test.csv'
|
||||
click_button 'Import'
|
||||
|
||||
expect(page).to have_selector '.item-count', text: "2"
|
||||
expect(page).to_not have_selector '.invalid-count'
|
||||
expect(page).to have_selector '.create-count', text: "1"
|
||||
expect(page).to have_selector '.update-count', text: "1"
|
||||
|
||||
expect(page).to_not have_selector '.reset-count'
|
||||
|
||||
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_selector '.created-count', text: '1'
|
||||
expect(page).to have_selector '.updated-count', text: '1'
|
||||
expect(page).to have_selector '.reset-count', text: '2'
|
||||
|
||||
Spree::Product.find_by_name('Carrots').on_hand.should == 5 # Present in file, added
|
||||
Spree::Product.find_by_name('Beans').on_hand.should == 6 # Present in file, updated
|
||||
Spree::Product.find_by_name('Sprouts').on_hand.should == 0 # In enterprise, not in file
|
||||
Spree::Product.find_by_name('Cabbage').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 "can reset all inventory items for an enterprise that are not present in the uploaded file to zero stock" do
|
||||
csv_data = CSV.generate do |csv|
|
||||
csv << ["name", "supplier", "producer", "category", "on_hand", "price", "unit_value"]
|
||||
csv << ["Beans", "Another Enterprise", "User Enterprise", "Vegetables", "6", "3.20", "500"]
|
||||
csv << ["Sprouts", "Another Enterprise", "User Enterprise", "Vegetables", "7", "6.50", "500"]
|
||||
end
|
||||
File.write('/tmp/test.csv', csv_data)
|
||||
|
||||
visit main_app.admin_product_import_path
|
||||
|
||||
attach_file 'file', '/tmp/test.csv'
|
||||
select 'Inventories', from: "settings_import_into", visible: false
|
||||
click_button 'Import'
|
||||
|
||||
expect(page).to have_selector '.item-count', text: "2"
|
||||
expect(page).to_not have_selector '.invalid-count'
|
||||
expect(page).to have_selector '.inv-create-count', text: "2"
|
||||
|
||||
expect(page).to_not have_selector '.reset-count'
|
||||
|
||||
within 'div.import-settings' do
|
||||
find('div.header-description').click # Import settings tab
|
||||
check "settings_#{enterprise2.id}_reset_all_absent"
|
||||
end
|
||||
|
||||
expect(page).to have_selector '.reset-count', text: "1"
|
||||
|
||||
click_button 'Save'
|
||||
|
||||
expect(page).to have_selector '.inv-created-count', text: '2'
|
||||
expect(page).to have_selector '.reset-count', text: '1'
|
||||
|
||||
beans = VariantOverride.where(variant_id: product2.variants.first.id, hub_id: enterprise2.id).first
|
||||
sprouts = VariantOverride.where(variant_id: product3.variants.first.id, hub_id: enterprise2.id).first
|
||||
cabbage = VariantOverride.where(variant_id: product4.variants.first.id, hub_id: enterprise2.id).first
|
||||
lettuce = VariantOverride.where(variant_id: product5.variants.first.id, hub_id: enterprise.id).first
|
||||
|
||||
beans.count_on_hand.should == 6 # Present in file, created
|
||||
sprouts.count_on_hand.should == 7 # Present in file, created
|
||||
cabbage.count_on_hand.should == 0 # In enterprise, not in file (reset)
|
||||
lettuce.count_on_hand.should == 96 # In different enterprise; unchanged
|
||||
end
|
||||
|
||||
it "can overwrite fields with selected defaults when importing to product list" 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"]
|
||||
csv << ["Carrots", "User Enterprise", "Vegetables", "5", "3.20", "500", "weight", "1", tax_category.id, ""]
|
||||
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
|
||||
expect(page).to have_selector "#settings_#{enterprise.id}_defaults_on_hand_mode", visible: false
|
||||
|
||||
# Overwrite stock level of all items to 9000
|
||||
check "settings_#{enterprise.id}_defaults_on_hand_active"
|
||||
select 'Overwrite all', from: "settings_#{enterprise.id}_defaults_on_hand_mode", visible: false
|
||||
fill_in "settings_#{enterprise.id}_defaults_on_hand_value", with: '9000'
|
||||
|
||||
# Overwrite default tax category, but only where field is empty
|
||||
check "settings_#{enterprise.id}_defaults_tax_category_id_active"
|
||||
select 'Overwrite if empty', from: "settings_#{enterprise.id}_defaults_tax_category_id_mode", visible: false
|
||||
select tax_category2.name, from: "settings_#{enterprise.id}_defaults_tax_category_id_value", visible: false
|
||||
|
||||
# Set default shipping category (field not present in file)
|
||||
check "settings_#{enterprise.id}_defaults_shipping_category_id_active"
|
||||
select 'Overwrite all', from: "settings_#{enterprise.id}_defaults_shipping_category_id_mode", visible: false
|
||||
select shipping_category.name, from: "settings_#{enterprise.id}_defaults_shipping_category_id_value", visible: false
|
||||
|
||||
# Set available_on date
|
||||
check "settings_#{enterprise.id}_defaults_available_on_active"
|
||||
select 'Overwrite all', from: "settings_#{enterprise.id}_defaults_available_on_mode", visible: false
|
||||
find("input#settings_#{enterprise.id}_defaults_available_on_value").set '2020-01-01'
|
||||
end
|
||||
|
||||
click_button 'Save'
|
||||
|
||||
expect(page).to have_selector '.created-count', text: '2'
|
||||
|
||||
carrots = Spree::Product.find_by_name('Carrots')
|
||||
carrots.on_hand.should == 9000
|
||||
carrots.tax_category_id.should == tax_category.id
|
||||
carrots.shipping_category_id.should == shipping_category.id
|
||||
carrots.available_on.should be_within(1.day).of(Time.zone.local(2020, 1, 1))
|
||||
|
||||
potatoes = Spree::Product.find_by_name('Potatoes')
|
||||
potatoes.on_hand.should == 9000
|
||||
potatoes.tax_category_id.should == tax_category2.id
|
||||
potatoes.shipping_category_id.should == shipping_category.id
|
||||
potatoes.available_on.should be_within(1.day).of(Time.zone.local(2020, 1, 1))
|
||||
end
|
||||
|
||||
it "can overwrite fields with selected defaults when importing to inventory" do
|
||||
csv_data = CSV.generate do |csv|
|
||||
csv << ["name", "producer", "supplier", "category", "on_hand", "price", "unit_value"]
|
||||
csv << ["Beans", "User Enterprise", "Another Enterprise", "Vegetables", "", "3.20", "500"]
|
||||
csv << ["Sprouts", "User Enterprise", "Another Enterprise", "Vegetables", "7", "6.50", "500"]
|
||||
csv << ["Cabbage", "User Enterprise", "Another Enterprise", "Vegetables", "", "1.50", "500"]
|
||||
end
|
||||
File.write('/tmp/test.csv', csv_data)
|
||||
|
||||
visit main_app.admin_product_import_path
|
||||
|
||||
attach_file 'file', '/tmp/test.csv'
|
||||
select 'Inventories', from: "settings_import_into", visible: false
|
||||
click_button 'Import'
|
||||
|
||||
within 'div.import-settings' do
|
||||
find('div.header-description').click # Import settings tab
|
||||
check "settings_#{enterprise2.id}_defaults_count_on_hand_active"
|
||||
select 'Overwrite if empty', from: "settings_#{enterprise2.id}_defaults_count_on_hand_mode", visible: false
|
||||
fill_in "settings_#{enterprise2.id}_defaults_count_on_hand_value", with: '9000'
|
||||
end
|
||||
|
||||
expect(page).to have_selector '.item-count', text: "3"
|
||||
expect(page).to_not have_selector '.invalid-count'
|
||||
expect(page).to_not have_selector '.create-count'
|
||||
expect(page).to_not have_selector '.update-count'
|
||||
expect(page).to have_selector '.inv-create-count', text: "2"
|
||||
expect(page).to have_selector '.inv-update-count', text: "1"
|
||||
|
||||
click_button 'Save'
|
||||
|
||||
expect(page).to_not have_selector '.created-count'
|
||||
expect(page).to_not have_selector '.updated-count'
|
||||
expect(page).to have_selector '.inv-created-count', text: '2'
|
||||
expect(page).to have_selector '.inv-updated-count', text: '1'
|
||||
|
||||
beans_override = VariantOverride.where(variant_id: product2.variants.first.id, hub_id: enterprise2.id).first
|
||||
sprouts_override = VariantOverride.where(variant_id: product3.variants.first.id, hub_id: enterprise2.id).first
|
||||
cabbage_override = VariantOverride.where(variant_id: product4.variants.first.id, hub_id: enterprise2.id).first
|
||||
|
||||
beans_override.count_on_hand.should == 9000
|
||||
sprouts_override.count_on_hand.should == 7
|
||||
cabbage_override.count_on_hand.should == 9000
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,25 +6,534 @@ describe ProductImporter do
|
||||
|
||||
let!(:admin) { create(:admin_user) }
|
||||
let!(:user) { create_enterprise_user }
|
||||
let!(:enterprise) { create(:enterprise, owner: user, name: "Test Enterprise") }
|
||||
let!(:user2) { create_enterprise_user }
|
||||
let!(:enterprise) { create(:enterprise, owner: user, name: "User Enterprise") }
|
||||
let!(:enterprise2) { create(:distributor_enterprise, owner: user2, name: "Another Enterprise") }
|
||||
let!(:relationship) { create(:enterprise_relationship, parent: enterprise, child: enterprise2, permissions_list: [:create_variant_overrides]) }
|
||||
|
||||
let!(:category) { create(:taxon, name: 'Vegetables') }
|
||||
let!(:category2) { create(:taxon, name: 'Cake') }
|
||||
let!(:tax_category) { create(:tax_category) }
|
||||
let!(:tax_category2) { create(:tax_category) }
|
||||
let!(:shipping_category) { create(:shipping_category) }
|
||||
|
||||
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', unit_value: '500') }
|
||||
let!(:product3) { create(:simple_product, supplier: enterprise, on_hand: '100', name: 'Sprouts', unit_value: '500') }
|
||||
let!(:product4) { create(:simple_product, supplier: enterprise, on_hand: '100', name: 'Cabbage', unit_value: '500') }
|
||||
let!(:product5) { create(:simple_product, supplier: enterprise2, on_hand: '100', name: 'Lettuce', unit_value: '500') }
|
||||
let!(:variant_override) { create(:variant_override, variant_id: product4.variants.first.id, hub: enterprise2, count_on_hand: 42) }
|
||||
let!(:variant_override2) { create(:variant_override, variant_id: product5.variants.first.id, hub: enterprise, count_on_hand: 96) }
|
||||
|
||||
let(:permissions) { OpenFoodNetwork::Permissions.new(user) }
|
||||
|
||||
describe "importing products from a spreadsheet" do
|
||||
after { File.delete('/tmp/test-m.csv') }
|
||||
|
||||
it "validates the entries" do
|
||||
before do
|
||||
csv_data = CSV.generate do |csv|
|
||||
csv << ["name", "supplier", "category", "on_hand", "price", "unit_value", "variant_unit", "variant_unit_scale"]
|
||||
csv << ["Carrots", "Test Enterprise", "Vegetables", "5", "3.20", "500", "weight", "1"]
|
||||
csv << ["Potatoes", "Test Enterprise", "Vegetables", "6", "6.50", "1000", "weight", "1000"]
|
||||
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-m.csv', csv_data)
|
||||
file = File.new('/tmp/test-m.csv')
|
||||
@importer = ProductImporter.new(file, admin, {start: 1, end: 100, import_into: 'product_list'})
|
||||
end
|
||||
after { File.delete('/tmp/test-m.csv') }
|
||||
|
||||
it "returns the number of entries" do
|
||||
expect(@importer.item_count).to eq(2)
|
||||
end
|
||||
|
||||
it "validates entries and returns the results as json" do
|
||||
@importer.validate_entries
|
||||
entries = JSON.parse(@importer.entries_json)
|
||||
|
||||
expect(filter('valid', entries)).to eq 2
|
||||
expect(filter('invalid', entries)).to eq 0
|
||||
expect(filter('create_product', entries)).to eq 2
|
||||
expect(filter('update_product', entries)).to eq 0
|
||||
end
|
||||
|
||||
it "saves the results and returns info on updated products" do
|
||||
@importer.save_entries
|
||||
|
||||
expect(@importer.products_created_count).to eq 2
|
||||
expect(@importer.updated_ids).to be_a(Array)
|
||||
expect(@importer.updated_ids.count).to eq 2
|
||||
|
||||
carrots = Spree::Product.find_by_name('Carrots')
|
||||
carrots.supplier.should == enterprise
|
||||
carrots.on_hand.should == 5
|
||||
carrots.price.should == 3.20
|
||||
carrots.variants.first.import_date.should be_within(1.minute).of DateTime.now
|
||||
|
||||
potatoes = Spree::Product.find_by_name('Potatoes')
|
||||
potatoes.supplier.should == enterprise
|
||||
potatoes.on_hand.should == 6
|
||||
potatoes.price.should == 6.50
|
||||
potatoes.variants.first.import_date.should be_within(1.minute).of DateTime.now
|
||||
end
|
||||
end
|
||||
|
||||
describe "when uploading a spreadsheet with some invalid entries" do
|
||||
before do
|
||||
csv_data = CSV.generate do |csv|
|
||||
csv << ["name", "supplier", "category", "on_hand", "price", "unit_value", "variant_unit", "variant_unit_scale"]
|
||||
csv << ["Good Carrots", "User Enterprise", "Vegetables", "5", "3.20", "500", "weight", "1"]
|
||||
csv << ["Bad Potatoes", "", "Vegetables", "6", "6.50", "1000", "", "1000"]
|
||||
end
|
||||
File.write('/tmp/test-m.csv', csv_data)
|
||||
file = File.new('/tmp/test-m.csv')
|
||||
@importer = ProductImporter.new(file, admin, {start: 1, end: 100, import_into: 'product_list'})
|
||||
end
|
||||
after { File.delete('/tmp/test-m.csv') }
|
||||
|
||||
it "validates entries" do
|
||||
@importer.validate_entries
|
||||
entries = JSON.parse(@importer.entries_json)
|
||||
|
||||
expect(filter('valid', entries)).to eq 1
|
||||
expect(filter('invalid', entries)).to eq 1
|
||||
expect(filter('create_product', entries)).to eq 1
|
||||
expect(filter('update_product', entries)).to eq 0
|
||||
end
|
||||
|
||||
it "allows saving of the valid entries" do
|
||||
@importer.save_entries
|
||||
|
||||
expect(@importer.products_created_count).to eq 1
|
||||
expect(@importer.updated_ids).to be_a(Array)
|
||||
expect(@importer.updated_ids.count).to eq 1
|
||||
|
||||
carrots = Spree::Product.find_by_name('Good Carrots')
|
||||
carrots.supplier.should == enterprise
|
||||
carrots.on_hand.should == 5
|
||||
carrots.price.should == 3.20
|
||||
carrots.variants.first.import_date.should be_within(1.minute).of DateTime.now
|
||||
|
||||
Spree::Product.find_by_name('Bad Potatoes').should == nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "adding new variants to existing products and updating exiting products" do
|
||||
before 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", "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-m.csv', csv_data)
|
||||
file = File.new('/tmp/test-m.csv')
|
||||
@importer = ProductImporter.new(file, admin, {start: 1, end: 100, import_into: 'product_list'})
|
||||
end
|
||||
after { File.delete('/tmp/test-m.csv') }
|
||||
|
||||
it "validates entries" do
|
||||
@importer.validate_entries
|
||||
entries = JSON.parse(@importer.entries_json)
|
||||
|
||||
expect(filter('valid', entries)).to eq 2
|
||||
expect(filter('invalid', entries)).to eq 0
|
||||
expect(filter('create_product', entries)).to eq 1
|
||||
expect(filter('update_product', entries)).to eq 1
|
||||
end
|
||||
|
||||
it "saves and updates" do
|
||||
@importer.save_entries
|
||||
|
||||
expect(@importer.products_created_count).to eq 1
|
||||
expect(@importer.products_updated_count).to eq 1
|
||||
expect(@importer.updated_ids).to be_a(Array)
|
||||
expect(@importer.updated_ids.count).to eq 2
|
||||
|
||||
added_coffee = Spree::Variant.find_by_display_name('Emergent Coffee')
|
||||
added_coffee.product.name.should == 'Hypothetical Cake'
|
||||
added_coffee.price.should == 3.50
|
||||
added_coffee.on_hand.should == 6
|
||||
added_coffee.import_date.should be_within(1.minute).of DateTime.now
|
||||
|
||||
updated_banana = Spree::Variant.find_by_display_name('Preexisting Banana')
|
||||
updated_banana.product.name.should == 'Hypothetical Cake'
|
||||
updated_banana.price.should == 5.50
|
||||
updated_banana.on_hand.should == 5
|
||||
updated_banana.import_date.should be_within(1.minute).of DateTime.now
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "adding new product and sub-variant at the same time" do
|
||||
before do
|
||||
csv_data = CSV.generate do |csv|
|
||||
csv << ["name", "supplier", "category", "on_hand", "price", "unit_value", "variant_unit", "variant_unit_scale", "display_name"]
|
||||
csv << ["Potatoes", "User Enterprise", "Vegetables", "5", "3.50", "500", "weight", "1000", "Small Bag"]
|
||||
csv << ["Potatoes", "User Enterprise", "Vegetables", "6", "5.50", "2000", "weight", "1000", "Big Bag"]
|
||||
end
|
||||
File.write('/tmp/test-m.csv', csv_data)
|
||||
file = File.new('/tmp/test-m.csv')
|
||||
@importer = ProductImporter.new(file, admin, {start: 1, end: 100, import_into: 'product_list'})
|
||||
end
|
||||
after { File.delete('/tmp/test-m.csv') }
|
||||
|
||||
it "validates entries" do
|
||||
@importer.validate_entries
|
||||
entries = JSON.parse(@importer.entries_json)
|
||||
|
||||
expect(filter('valid', entries)).to eq 2
|
||||
expect(filter('invalid', entries)).to eq 0
|
||||
expect(filter('create_product', entries)).to eq 2
|
||||
end
|
||||
|
||||
it "saves and updates" do
|
||||
@importer.save_entries
|
||||
|
||||
expect(@importer.products_created_count).to eq 2
|
||||
expect(@importer.updated_ids).to be_a(Array)
|
||||
expect(@importer.updated_ids.count).to eq 2
|
||||
|
||||
small_bag = Spree::Variant.find_by_display_name('Small Bag')
|
||||
small_bag.product.name.should == 'Potatoes'
|
||||
small_bag.price.should == 3.50
|
||||
small_bag.on_hand.should == 5
|
||||
|
||||
big_bag = Spree::Variant.find_by_display_name('Big Bag')
|
||||
big_bag.product.name.should == 'Potatoes'
|
||||
big_bag.price.should == 5.50
|
||||
big_bag.on_hand.should == 6
|
||||
end
|
||||
end
|
||||
|
||||
describe "importing items into inventory" do
|
||||
before do
|
||||
csv_data = CSV.generate do |csv|
|
||||
csv << ["name", "supplier", "producer", "category", "on_hand", "price", "unit_value"]
|
||||
csv << ["Beans", "Another Enterprise", "User Enterprise", "Vegetables", "5", "3.20", "500"]
|
||||
csv << ["Sprouts", "Another Enterprise", "User Enterprise", "Vegetables", "6", "6.50", "500"]
|
||||
csv << ["Cabbage", "Another Enterprise", "User Enterprise", "Vegetables", "2001", "1.50", "500"]
|
||||
end
|
||||
File.write('/tmp/test-m.csv', csv_data)
|
||||
file = File.new('/tmp/test-m.csv')
|
||||
@importer = ProductImporter.new(file, admin, {start: 1, end: 100, import_into: 'inventories'})
|
||||
end
|
||||
after { File.delete('/tmp/test-m.csv') }
|
||||
|
||||
it "validates entries" do
|
||||
@importer.validate_entries
|
||||
entries = JSON.parse(@importer.entries_json)
|
||||
|
||||
expect(filter('valid', entries)).to eq 3
|
||||
expect(filter('invalid', entries)).to eq 0
|
||||
expect(filter('create_inventory', entries)).to eq 2
|
||||
expect(filter('update_inventory', entries)).to eq 1
|
||||
end
|
||||
|
||||
it "saves and updates inventory" do
|
||||
@importer.save_entries
|
||||
|
||||
expect(@importer.inventory_created_count).to eq 2
|
||||
expect(@importer.inventory_updated_count).to eq 1
|
||||
expect(@importer.updated_ids).to be_a(Array)
|
||||
expect(@importer.updated_ids.count).to eq 3
|
||||
|
||||
beans_override = VariantOverride.where(variant_id: product2.variants.first.id, hub_id: enterprise2.id).first
|
||||
sprouts_override = VariantOverride.where(variant_id: product3.variants.first.id, hub_id: enterprise2.id).first
|
||||
cabbage_override = VariantOverride.where(variant_id: product4.variants.first.id, hub_id: enterprise2.id).first
|
||||
|
||||
Float(beans_override.price).should == 3.20
|
||||
beans_override.count_on_hand.should == 5
|
||||
|
||||
Float(sprouts_override.price).should == 6.50
|
||||
sprouts_override.count_on_hand.should == 6
|
||||
|
||||
Float(cabbage_override.price).should == 1.50
|
||||
cabbage_override.count_on_hand.should == 2001
|
||||
end
|
||||
end
|
||||
|
||||
describe "handling enterprise permissions" do
|
||||
after { File.delete('/tmp/test-m.csv') }
|
||||
|
||||
it "only allows product import into enterprises the user is permitted to manage" do
|
||||
csv_data = CSV.generate do |csv|
|
||||
csv << ["name", "supplier", "category", "on_hand", "price", "unit_value", "variant_unit", "variant_unit_scale"]
|
||||
csv << ["My Carrots", "User Enterprise", "Vegetables", "5", "3.20", "500", "weight", "1"]
|
||||
csv << ["Your Potatoes", "Another Enterprise", "Vegetables", "6", "6.50", "1000", "weight", "1000"]
|
||||
end
|
||||
File.write('/tmp/test-m.csv', csv_data)
|
||||
file = File.new('/tmp/test-m.csv')
|
||||
@importer = ProductImporter.new(file, user, {start: 1, end: 100, import_into: 'product_list'})
|
||||
|
||||
@importer.validate_entries
|
||||
entries = JSON.parse(@importer.entries_json)
|
||||
|
||||
expect(filter('valid', entries)).to eq 1
|
||||
expect(filter('invalid', entries)).to eq 1
|
||||
expect(filter('create_product', entries)).to eq 1
|
||||
|
||||
@importer.save_entries
|
||||
|
||||
expect(@importer.products_created_count).to eq 1
|
||||
expect(@importer.updated_ids).to be_a(Array)
|
||||
expect(@importer.updated_ids.count).to eq 1
|
||||
|
||||
Spree::Product.find_by_name('My Carrots').should be_a Spree::Product
|
||||
Spree::Product.find_by_name('Your Potatoes').should == nil
|
||||
end
|
||||
|
||||
it "allows creating inventories for producers that a user's hub has permission for" do
|
||||
csv_data = CSV.generate do |csv|
|
||||
csv << ["name", "producer", "supplier", "category", "on_hand", "price", "unit_value"]
|
||||
csv << ["Beans", "User Enterprise", "Another Enterprise", "Vegetables", "777", "3.20", "500"]
|
||||
end
|
||||
File.write('/tmp/test-m.csv', csv_data)
|
||||
file = File.new('/tmp/test-m.csv')
|
||||
@importer = ProductImporter.new(file, user2, {start: 1, end: 100, import_into: 'inventories'})
|
||||
|
||||
@importer.validate_entries
|
||||
entries = JSON.parse(@importer.entries_json)
|
||||
|
||||
expect(filter('valid', entries)).to eq 1
|
||||
expect(filter('invalid', entries)).to eq 0
|
||||
expect(filter('create_inventory', entries)).to eq 1
|
||||
|
||||
@importer.save_entries
|
||||
|
||||
expect(@importer.inventory_created_count).to eq 1
|
||||
expect(@importer.updated_ids).to be_a(Array)
|
||||
expect(@importer.updated_ids.count).to eq 1
|
||||
|
||||
beans = VariantOverride.where(variant_id: product2.variants.first.id, hub_id: enterprise2.id).first
|
||||
beans.count_on_hand.should == 777
|
||||
end
|
||||
|
||||
it "does not allow creating inventories for producers that a user's hubs don't have permission for" do
|
||||
csv_data = CSV.generate do |csv|
|
||||
csv << ["name", "supplier", "category", "on_hand", "price", "unit_value"]
|
||||
csv << ["Beans", "User Enterprise", "Vegetables", "5", "3.20", "500"]
|
||||
csv << ["Sprouts", "User Enterprise", "Vegetables", "6", "6.50", "500"]
|
||||
end
|
||||
File.write('/tmp/test-m.csv', csv_data)
|
||||
file = File.new('/tmp/test-m.csv')
|
||||
@importer = ProductImporter.new(file, user2, {start: 1, end: 100, import_into: 'inventories'})
|
||||
|
||||
@importer.validate_entries
|
||||
entries = JSON.parse(@importer.entries_json)
|
||||
|
||||
expect(filter('valid', entries)).to eq 0
|
||||
expect(filter('invalid', entries)).to eq 2
|
||||
expect(filter('create_inventory', entries)).to eq 0
|
||||
|
||||
@importer.save_entries
|
||||
|
||||
expect(@importer.inventory_created_count).to eq 0
|
||||
expect(@importer.updated_ids).to be_a(Array)
|
||||
expect(@importer.updated_ids.count).to eq 0
|
||||
end
|
||||
end
|
||||
|
||||
describe "applying settings and defaults on import" do
|
||||
after { File.delete('/tmp/test-m.csv') }
|
||||
|
||||
it "can reset 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 << ["Beans", "User Enterprise", "Vegetables", "6", "6.50", "500", "weight", "1"]
|
||||
end
|
||||
File.write('/tmp/test-m.csv', csv_data)
|
||||
file = File.new('/tmp/test-m.csv')
|
||||
|
||||
importer = ProductImporter.new(file, admin)
|
||||
@importer = ProductImporter.new(file, admin, {start: 1, end: 100, import_into: 'product_list', 'settings' => {enterprise.id => {'reset_all_absent' => true}}})
|
||||
|
||||
expect(importer.item_count).to eq(2)
|
||||
@importer.validate_entries
|
||||
entries = JSON.parse(@importer.entries_json)
|
||||
|
||||
expect(filter('valid', entries)).to eq 2
|
||||
expect(filter('invalid', entries)).to eq 0
|
||||
expect(filter('create_product', entries)).to eq 1
|
||||
expect(filter('update_product', entries)).to eq 1
|
||||
|
||||
@importer.save_entries
|
||||
|
||||
expect(@importer.products_created_count).to eq 1
|
||||
expect(@importer.products_updated_count).to eq 1
|
||||
expect(@importer.updated_ids).to be_a(Array)
|
||||
expect(@importer.updated_ids.count).to eq 2
|
||||
|
||||
@importer.reset_absent(@importer.updated_ids)
|
||||
|
||||
expect(@importer.products_reset_count).to eq 2
|
||||
|
||||
Spree::Product.find_by_name('Carrots').on_hand.should == 5 # Present in file, added
|
||||
Spree::Product.find_by_name('Beans').on_hand.should == 6 # Present in file, updated
|
||||
Spree::Product.find_by_name('Sprouts').on_hand.should == 0 # In enterprise, not in file
|
||||
Spree::Product.find_by_name('Cabbage').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 "can reset all inventory items for an enterprise that are not present in the uploaded file to zero stock" do
|
||||
csv_data = CSV.generate do |csv|
|
||||
csv << ["name", "supplier", "producer", "category", "on_hand", "price", "unit_value"]
|
||||
csv << ["Beans", "Another Enterprise", "User Enterprise", "Vegetables", "6", "3.20", "500"]
|
||||
csv << ["Sprouts", "Another Enterprise", "User Enterprise", "Vegetables", "7", "6.50", "500"]
|
||||
end
|
||||
File.write('/tmp/test-m.csv', csv_data)
|
||||
file = File.new('/tmp/test-m.csv')
|
||||
@importer = ProductImporter.new(file, admin, {start: 1, end: 100, import_into: 'inventories', 'settings' => {enterprise2.id => {'reset_all_absent' => true}}})
|
||||
|
||||
@importer.validate_entries
|
||||
entries = JSON.parse(@importer.entries_json)
|
||||
|
||||
expect(filter('valid', entries)).to eq 2
|
||||
expect(filter('invalid', entries)).to eq 0
|
||||
expect(filter('create_inventory', entries)).to eq 2
|
||||
|
||||
@importer.save_entries
|
||||
|
||||
expect(@importer.inventory_created_count).to eq 2
|
||||
expect(@importer.updated_ids).to be_a(Array)
|
||||
expect(@importer.updated_ids.count).to eq 2
|
||||
|
||||
@importer.reset_absent(@importer.updated_ids)
|
||||
|
||||
expect(@importer.products_reset_count).to eq 1
|
||||
|
||||
beans = VariantOverride.where(variant_id: product2.variants.first.id, hub_id: enterprise2.id).first
|
||||
sprouts = VariantOverride.where(variant_id: product3.variants.first.id, hub_id: enterprise2.id).first
|
||||
cabbage = VariantOverride.where(variant_id: product4.variants.first.id, hub_id: enterprise2.id).first
|
||||
lettuce = VariantOverride.where(variant_id: product5.variants.first.id, hub_id: enterprise.id).first
|
||||
|
||||
beans.count_on_hand.should == 6 # Present in file, created
|
||||
sprouts.count_on_hand.should == 7 # Present in file, created
|
||||
cabbage.count_on_hand.should == 0 # In enterprise, not in file (reset)
|
||||
lettuce.count_on_hand.should == 96 # In different enterprise; unchanged
|
||||
end
|
||||
|
||||
it "can overwrite fields with selected defaults when importing to product list" 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"]
|
||||
csv << ["Carrots", "User Enterprise", "Vegetables", "5", "3.20", "500", "weight", "1", tax_category.id, ""]
|
||||
csv << ["Potatoes", "User Enterprise", "Vegetables", "6", "6.50", "1000", "weight", "1000", "", ""]
|
||||
end
|
||||
File.write('/tmp/test-m.csv', csv_data)
|
||||
file = File.new('/tmp/test-m.csv')
|
||||
|
||||
import_settings = {enterprise.id.to_s => {
|
||||
'defaults' => {
|
||||
'on_hand' => {
|
||||
'active' => true,
|
||||
'mode' => 'overwrite_all',
|
||||
'value' => '9000'
|
||||
},
|
||||
'tax_category_id' => {
|
||||
'active' => true,
|
||||
'mode' => 'overwrite_empty',
|
||||
'value' => tax_category2.id
|
||||
},
|
||||
'shipping_category_id' => {
|
||||
'active' => true,
|
||||
'mode' => 'overwrite_all',
|
||||
'value' => shipping_category.id
|
||||
},
|
||||
'available_on' => {
|
||||
'active' => true,
|
||||
'mode' => 'overwrite_all',
|
||||
'value' => '2020-01-01'
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
@importer = ProductImporter.new(file, admin, {start: 1, end: 100, import_into: 'product_list', settings: import_settings})
|
||||
|
||||
@importer.validate_entries
|
||||
entries = JSON.parse(@importer.entries_json)
|
||||
|
||||
expect(filter('valid', entries)).to eq 2
|
||||
expect(filter('invalid', entries)).to eq 0
|
||||
expect(filter('create_product', entries)).to eq 2
|
||||
|
||||
@importer.save_entries
|
||||
|
||||
expect(@importer.products_created_count).to eq 2
|
||||
expect(@importer.updated_ids).to be_a(Array)
|
||||
expect(@importer.updated_ids.count).to eq 2
|
||||
|
||||
carrots = Spree::Product.find_by_name('Carrots')
|
||||
carrots.on_hand.should == 9000
|
||||
carrots.tax_category_id.should == tax_category.id
|
||||
carrots.shipping_category_id.should == shipping_category.id
|
||||
carrots.available_on.should be_within(1.day).of(Time.zone.local(2020, 1, 1))
|
||||
|
||||
potatoes = Spree::Product.find_by_name('Potatoes')
|
||||
potatoes.on_hand.should == 9000
|
||||
potatoes.tax_category_id.should == tax_category2.id
|
||||
potatoes.shipping_category_id.should == shipping_category.id
|
||||
potatoes.available_on.should be_within(1.day).of(Time.zone.local(2020, 1, 1))
|
||||
end
|
||||
|
||||
it "can overwrite fields with selected defaults when importing to inventory" do
|
||||
csv_data = CSV.generate do |csv|
|
||||
csv << ["name", "producer", "supplier", "category", "on_hand", "price", "unit_value"]
|
||||
csv << ["Beans", "User Enterprise", "Another Enterprise", "Vegetables", "", "3.20", "500"]
|
||||
csv << ["Sprouts", "User Enterprise", "Another Enterprise", "Vegetables", "7", "6.50", "500"]
|
||||
csv << ["Cabbage", "User Enterprise", "Another Enterprise", "Vegetables", "", "1.50", "500"]
|
||||
end
|
||||
File.write('/tmp/test-m.csv', csv_data)
|
||||
file = File.new('/tmp/test-m.csv')
|
||||
|
||||
import_settings = {enterprise2.id.to_s => {
|
||||
'defaults' => {
|
||||
'count_on_hand' => {
|
||||
'active' => true,
|
||||
'mode' => 'overwrite_empty',
|
||||
'value' => '9000'
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
@importer = ProductImporter.new(file, admin, {start: 1, end: 100, import_into: 'inventories', settings: import_settings})
|
||||
|
||||
@importer.validate_entries
|
||||
entries = JSON.parse(@importer.entries_json)
|
||||
|
||||
expect(filter('valid', entries)).to eq 3
|
||||
expect(filter('invalid', entries)).to eq 0
|
||||
expect(filter('create_inventory', entries)).to eq 2
|
||||
expect(filter('update_inventory', entries)).to eq 1
|
||||
|
||||
@importer.save_entries
|
||||
|
||||
expect(@importer.inventory_created_count).to eq 2
|
||||
expect(@importer.inventory_updated_count).to eq 1
|
||||
expect(@importer.updated_ids).to be_a(Array)
|
||||
expect(@importer.updated_ids.count).to eq 3
|
||||
|
||||
beans_override = VariantOverride.where(variant_id: product2.variants.first.id, hub_id: enterprise2.id).first
|
||||
sprouts_override = VariantOverride.where(variant_id: product3.variants.first.id, hub_id: enterprise2.id).first
|
||||
cabbage_override = VariantOverride.where(variant_id: product4.variants.first.id, hub_id: enterprise2.id).first
|
||||
|
||||
beans_override.count_on_hand.should == 9000
|
||||
sprouts_override.count_on_hand.should == 7
|
||||
cabbage_override.count_on_hand.should == 9000
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def filter(type, entries)
|
||||
valid_count = 0
|
||||
entries.each do |line_number, entry|
|
||||
validates_as = entry['validates_as']
|
||||
|
||||
valid_count += 1 if type == 'valid' and (validates_as != '')
|
||||
valid_count += 1 if type == 'invalid' and (validates_as == '')
|
||||
valid_count += 1 if type == 'create_product' and (validates_as == 'new_product' or validates_as == 'new_variant')
|
||||
valid_count += 1 if type == 'update_product' and validates_as == 'existing_variant'
|
||||
valid_count += 1 if type == 'create_inventory' and validates_as == 'new_inventory_item'
|
||||
valid_count += 1 if type == 'update_inventory' and validates_as == 'existing_inventory_item'
|
||||
end
|
||||
valid_count
|
||||
end
|
||||
Reference in New Issue
Block a user