Import multiple spreadsheet formats. Make upload work like sync.
This commit is contained in:
parent
08c8d25a9d
commit
07ba6f0535
8 changed files with 157 additions and 191 deletions
2
Gemfile
2
Gemfile
|
@ -42,6 +42,8 @@ gem 'ruby-units'
|
||||||
gem 'attribute_normalizer'
|
gem 'attribute_normalizer'
|
||||||
gem 'ice_cube', github: 'wvengen/ice_cube', branch: 'issues/50-from_ical-rebased' # fork until merged
|
gem 'ice_cube', github: 'wvengen/ice_cube', branch: 'issues/50-from_ical-rebased' # fork until merged
|
||||||
gem 'recurring_select'
|
gem 'recurring_select'
|
||||||
|
gem 'roo', '~> 1.13.2'
|
||||||
|
gem 'spreadsheet'
|
||||||
|
|
||||||
# we use the git version of acts_as_versioned, and need to include it in this Gemfile
|
# we use the git version of acts_as_versioned, and need to include it in this Gemfile
|
||||||
gem 'acts_as_versioned', github: 'technoweenie/acts_as_versioned'
|
gem 'acts_as_versioned', github: 'technoweenie/acts_as_versioned'
|
||||||
|
|
|
@ -333,6 +333,10 @@ GEM
|
||||||
http-cookie (>= 1.0.2, < 2.0)
|
http-cookie (>= 1.0.2, < 2.0)
|
||||||
mime-types (>= 1.16, < 3.0)
|
mime-types (>= 1.16, < 3.0)
|
||||||
netrc (~> 0.7)
|
netrc (~> 0.7)
|
||||||
|
roo (1.13.2)
|
||||||
|
nokogiri
|
||||||
|
rubyzip
|
||||||
|
spreadsheet (> 0.6.4)
|
||||||
rspec (2.99.0)
|
rspec (2.99.0)
|
||||||
rspec-core (~> 2.99.0)
|
rspec-core (~> 2.99.0)
|
||||||
rspec-expectations (~> 2.99.0)
|
rspec-expectations (~> 2.99.0)
|
||||||
|
@ -354,6 +358,7 @@ GEM
|
||||||
rspec-mocks (~> 2.99.0)
|
rspec-mocks (~> 2.99.0)
|
||||||
rspec-rerun (0.3.0)
|
rspec-rerun (0.3.0)
|
||||||
rspec
|
rspec
|
||||||
|
ruby-ole (1.2.11.8)
|
||||||
ruby-prof (0.15.6)
|
ruby-prof (0.15.6)
|
||||||
ruby-units (1.4.5)
|
ruby-units (1.4.5)
|
||||||
ruby_parser (3.6.5)
|
ruby_parser (3.6.5)
|
||||||
|
@ -395,6 +400,8 @@ GEM
|
||||||
eventmachine (~> 1.0.0)
|
eventmachine (~> 1.0.0)
|
||||||
thin (~> 1.5.0)
|
thin (~> 1.5.0)
|
||||||
slop (3.6.0)
|
slop (3.6.0)
|
||||||
|
spreadsheet (1.0.0)
|
||||||
|
ruby-ole (>= 1.0)
|
||||||
sprockets (2.12.3)
|
sprockets (2.12.3)
|
||||||
hike (~> 1.2)
|
hike (~> 1.2)
|
||||||
multi_json (~> 1.0)
|
multi_json (~> 1.0)
|
||||||
|
@ -510,6 +517,7 @@ DEPENDENCIES
|
||||||
ransack
|
ransack
|
||||||
recurring_select
|
recurring_select
|
||||||
resque
|
resque
|
||||||
|
roo (~> 1.13.2)
|
||||||
rspec-core (~> 2.99)
|
rspec-core (~> 2.99)
|
||||||
rspec-rails
|
rspec-rails
|
||||||
rspec-rerun
|
rspec-rerun
|
||||||
|
@ -522,6 +530,7 @@ DEPENDENCIES
|
||||||
simple-navigation-bootstrap
|
simple-navigation-bootstrap
|
||||||
simple_form
|
simple_form
|
||||||
simplecov
|
simplecov
|
||||||
|
spreadsheet
|
||||||
sqlite3
|
sqlite3
|
||||||
therubyracer
|
therubyracer
|
||||||
thin
|
thin
|
||||||
|
|
|
@ -135,88 +135,36 @@ class ArticlesController < ApplicationController
|
||||||
def upload
|
def upload
|
||||||
end
|
end
|
||||||
|
|
||||||
# parses the articles from a csv and creates a form-table with the parsed data.
|
# Update articles from a spreadsheet
|
||||||
# the csv must have the following format:
|
|
||||||
# status | number | name | note | manufacturer | origin | unit | clear price | unit_quantity | tax | deposit | scale quantity | scale price | category
|
|
||||||
# the first line will be ignored.
|
|
||||||
# field-seperator: ";"
|
|
||||||
# text-seperator: ""
|
|
||||||
def parse_upload
|
def parse_upload
|
||||||
begin
|
uploaded_file = params[:articles]['file']
|
||||||
@articles = Array.new
|
options = {filename: uploaded_file.original_filename}
|
||||||
articles, outlisted_articles = FoodsoftFile::parse(params[:articles]["file"])
|
@updated_article_pairs, @outlisted_articles, @new_articles = [], [], []
|
||||||
no_category = ArticleCategory.new
|
FoodsoftFile::parse uploaded_file.tempfile, options do |status, new_attrs, line|
|
||||||
articles.each do |row|
|
article = @supplier.articles.where(order_number: new_attrs[:order_number]).first
|
||||||
# fallback to Others category
|
new_attrs[:article_category] = ArticleCategory.find_match(new_attrs[:article_category])
|
||||||
category = (ArticleCategory.find_match(row[:category]) || no_category)
|
new_attrs[:tax] ||= FoodsoftConfig[:tax_default]
|
||||||
# creates a new article and price
|
new_article = @supplier.articles.build(new_attrs)
|
||||||
article = @supplier.articles.build(:name => row[:name],
|
|
||||||
:note => row[:note],
|
if status.nil? && article.nil?
|
||||||
:manufacturer => row[:manufacturer],
|
@new_articles << new_article
|
||||||
:origin => row[:origin],
|
elsif status.nil? && article.present?
|
||||||
:unit => row[:unit],
|
unequal_attributes = article.unequal_attributes(new_article)
|
||||||
:article_category => category,
|
article.attributes = unequal_attributes
|
||||||
:price => row[:price],
|
@updated_article_pairs << [article, unequal_attributes]
|
||||||
:unit_quantity => row[:unit_quantity],
|
elsif status == :outlisted && article.present?
|
||||||
:order_number => row[:number],
|
@outlisted_articles << article
|
||||||
:deposit => row[:deposit],
|
|
||||||
:tax => (row[:tax] || FoodsoftConfig[:tax_default]))
|
# stop when there is a parsing error
|
||||||
# stop parsing, when an article isn't valid
|
elsif status.is_a? String
|
||||||
unless article.valid?
|
raise I18n.t('articles.controller.error_parse', :msg => status, :line => line.to_s)
|
||||||
raise I18n.t('articles.controller.error_parse', :msg => article.errors.full_messages.join(", "), :line => (articles.index(row) + 2).to_s)
|
|
||||||
end
|
|
||||||
@articles << article
|
|
||||||
end
|
end
|
||||||
flash.now[:notice] = I18n.t('articles.controller.parse_upload.notice', :count => @articles.size)
|
|
||||||
rescue => error
|
|
||||||
redirect_to upload_supplier_articles_path(@supplier), :alert => I18n.t('errors.general_msg', :msg => error.message)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# creates articles from form
|
|
||||||
def create_from_upload
|
|
||||||
begin
|
|
||||||
Article.transaction do
|
|
||||||
invalid_articles = false
|
|
||||||
@articles = []
|
|
||||||
params[:articles].each do |_key, article_attributes|
|
|
||||||
@articles << (article = @supplier.articles.build(article_attributes))
|
|
||||||
invalid_articles = true unless article.save
|
|
||||||
end
|
|
||||||
|
|
||||||
raise I18n.t('articles.controller.error_invalid') if invalid_articles
|
|
||||||
end
|
|
||||||
# Successfully done.
|
|
||||||
redirect_to supplier_articles_path(@supplier), notice: I18n.t('articles.controller.create_from_upload.notice', :count => @articles.size)
|
|
||||||
|
|
||||||
rescue => error
|
|
||||||
# An error has occurred, transaction has been rolled back.
|
|
||||||
flash.now[:error] = I18n.t('errors.general_msg', :msg => error.message)
|
|
||||||
render :parse_upload
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# renders a view to import articles in local database
|
|
||||||
#
|
|
||||||
def shared
|
|
||||||
# build array of keywords, required for ransack _all suffix
|
|
||||||
params[:q][:name_cont_all] = params[:q][:name_cont_all].split(' ') if params[:q]
|
|
||||||
# Build search with meta search plugin
|
|
||||||
@search = @supplier.shared_supplier.shared_articles.search(params[:q])
|
|
||||||
@articles = @search.result.page(params[:page]).per(10)
|
|
||||||
render :layout => false
|
|
||||||
end
|
|
||||||
|
|
||||||
# fills a form whith values of the selected shared_article
|
|
||||||
# when the direct parameter is set and the article is valid, it is imported directly
|
|
||||||
def import
|
|
||||||
@article = SharedArticle.find(params[:shared_article_id]).build_new_article(@supplier)
|
|
||||||
@article.article_category_id = params[:article_category_id] unless params[:article_category_id].blank?
|
|
||||||
if params[:direct] && !params[:article_category_id].blank? && @article.valid? && @article.save
|
|
||||||
render :action => 'create', :layout => false
|
|
||||||
else
|
|
||||||
render :action => 'new', :layout => false
|
|
||||||
end
|
end
|
||||||
|
@ignored_article_count = 0
|
||||||
|
render :sync
|
||||||
|
rescue => error
|
||||||
|
redirect_to upload_supplier_articles_path(@supplier), :alert => I18n.t('errors.general_msg', :msg => error.message)
|
||||||
end
|
end
|
||||||
|
|
||||||
# sync all articles with the external database
|
# sync all articles with the external database
|
||||||
|
@ -227,16 +175,14 @@ class ArticlesController < ApplicationController
|
||||||
redirect_to supplier_articles_url(@supplier), :alert => I18n.t('articles.controller.sync.shared_alert', :supplier => @supplier.name)
|
redirect_to supplier_articles_url(@supplier), :alert => I18n.t('articles.controller.sync.shared_alert', :supplier => @supplier.name)
|
||||||
end
|
end
|
||||||
# sync articles against external database
|
# sync articles against external database
|
||||||
@updated_articles, @outlisted_articles, @new_articles = @supplier.sync_all
|
@updated_article_pairs, @outlisted_articles, @new_articles = @supplier.sync_all
|
||||||
# convert to db-compatible-string
|
if @updated_article_pairs.empty? && @outlisted_articles.empty? && @new_articles.empty?
|
||||||
@updated_articles.each {|a, b| a.shared_updated_on = a.shared_updated_on.to_formatted_s(:db)}
|
|
||||||
if @updated_articles.empty? && @outlisted_articles.empty? && @new_articles.empty?
|
|
||||||
redirect_to supplier_articles_path(@supplier), :notice => I18n.t('articles.controller.sync.notice')
|
redirect_to supplier_articles_path(@supplier), :notice => I18n.t('articles.controller.sync.notice')
|
||||||
end
|
end
|
||||||
@ignored_article_count = @supplier.articles.where(order_number: [nil, '']).count
|
@ignored_article_count = @supplier.articles.where(order_number: [nil, '']).count
|
||||||
end
|
end
|
||||||
|
|
||||||
# Updates, deletes articles when sync form is submitted
|
# Updates, deletes articles when upload or sync form is submitted
|
||||||
def update_synchronized
|
def update_synchronized
|
||||||
begin
|
begin
|
||||||
Article.transaction do
|
Article.transaction do
|
||||||
|
@ -277,4 +223,27 @@ class ArticlesController < ApplicationController
|
||||||
alert: I18n.t('errors.general_msg', :msg => error.message)
|
alert: I18n.t('errors.general_msg', :msg => error.message)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# renders a view to import articles in local database
|
||||||
|
#
|
||||||
|
def shared
|
||||||
|
# build array of keywords, required for ransack _all suffix
|
||||||
|
params[:q][:name_cont_all] = params[:q][:name_cont_all].split(' ') if params[:q]
|
||||||
|
# Build search with meta search plugin
|
||||||
|
@search = @supplier.shared_supplier.shared_articles.search(params[:q])
|
||||||
|
@articles = @search.result.page(params[:page]).per(10)
|
||||||
|
render :layout => false
|
||||||
|
end
|
||||||
|
|
||||||
|
# fills a form whith values of the selected shared_article
|
||||||
|
# when the direct parameter is set and the article is valid, it is imported directly
|
||||||
|
def import
|
||||||
|
@article = SharedArticle.find(params[:shared_article_id]).build_new_article(@supplier)
|
||||||
|
@article.article_category_id = params[:article_category_id] unless params[:article_category_id].blank?
|
||||||
|
if params[:direct] && !params[:article_category_id].blank? && @article.valid? && @article.save
|
||||||
|
render :action => 'create', :layout => false
|
||||||
|
else
|
||||||
|
render :action => 'new', :layout => false
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,7 +3,7 @@ module ArticlesHelper
|
||||||
# useful for highlighting attributes, when synchronizing articles
|
# useful for highlighting attributes, when synchronizing articles
|
||||||
def highlight_new(unequal_attributes, attribute)
|
def highlight_new(unequal_attributes, attribute)
|
||||||
return unless unequal_attributes
|
return unless unequal_attributes
|
||||||
unequal_attributes.detect {|a| a == attribute} ? "background-color: yellow" : ""
|
unequal_attributes.has_key?(attribute) ? "background-color: yellow" : ""
|
||||||
end
|
end
|
||||||
|
|
||||||
def row_classes(article)
|
def row_classes(article)
|
||||||
|
|
|
@ -104,46 +104,55 @@ class Article < ActiveRecord::Base
|
||||||
# skip early if the timestamp hasn't changed
|
# skip early if the timestamp hasn't changed
|
||||||
shared_article = self.shared_article(supplier)
|
shared_article = self.shared_article(supplier)
|
||||||
unless shared_article.nil? || self.shared_updated_on == shared_article.updated_on
|
unless shared_article.nil? || self.shared_updated_on == shared_article.updated_on
|
||||||
|
attrs = unequal_attributes(shared_article)
|
||||||
# try to convert units
|
if attrs.empty?
|
||||||
# convert supplier's price and unit_quantity into fc-size
|
|
||||||
new_price, new_unit_quantity = self.convert_units
|
|
||||||
new_unit = self.unit
|
|
||||||
unless new_price && new_unit_quantity
|
|
||||||
# if convertion isn't possible, take shared_article-price/unit_quantity
|
|
||||||
new_price, new_unit_quantity, new_unit = shared_article.price, shared_article.unit_quantity, shared_article.unit
|
|
||||||
end
|
|
||||||
|
|
||||||
# check if all attributes differ
|
|
||||||
unequal_attributes = Article.compare_attributes(
|
|
||||||
{
|
|
||||||
:name => [self.name, shared_article.name],
|
|
||||||
:manufacturer => [self.manufacturer, shared_article.manufacturer.to_s],
|
|
||||||
:origin => [self.origin, shared_article.origin],
|
|
||||||
:unit => [self.unit, new_unit],
|
|
||||||
:price => [self.price.to_f.round(2), new_price.to_f.round(2)],
|
|
||||||
:tax => [self.tax, shared_article.tax],
|
|
||||||
:deposit => [self.deposit.to_f.round(2), shared_article.deposit.to_f.round(2)],
|
|
||||||
# take care of different num-objects.
|
|
||||||
:unit_quantity => [self.unit_quantity.to_s.to_f, new_unit_quantity.to_s.to_f],
|
|
||||||
:note => [self.note.to_s, shared_article.note.to_s]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if unequal_attributes.empty?
|
|
||||||
# when attributes not changed, update timestamp of article
|
# when attributes not changed, update timestamp of article
|
||||||
self.update_attribute(:shared_updated_on, shared_article.updated_on)
|
self.update_attribute(:shared_updated_on, shared_article.updated_on)
|
||||||
false
|
false
|
||||||
else
|
else
|
||||||
unequal_attributes
|
attrs
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# compare attributes from different articles. used for auto-synchronization
|
# Return article attributes that were changed (incl. unit conversion)
|
||||||
# returns array of symbolized unequal attributes
|
# @param [Article] New article to update self
|
||||||
|
# @return [Hash<Symbol, Object>] Attributes with new values
|
||||||
|
def unequal_attributes(new_article)
|
||||||
|
# try to convert different units
|
||||||
|
new_price, new_unit_quantity = convert_units(new_article)
|
||||||
|
if new_price && new_unit_quantity
|
||||||
|
new_unit = self.unit
|
||||||
|
else
|
||||||
|
new_price = new_article.price
|
||||||
|
new_unit_quantity = new_article.unit_quantity
|
||||||
|
new_unit = new_article.unit
|
||||||
|
end
|
||||||
|
|
||||||
|
return Article.compare_attributes(
|
||||||
|
{
|
||||||
|
:name => [self.name, new_article.name],
|
||||||
|
:manufacturer => [self.manufacturer, new_article.manufacturer.to_s],
|
||||||
|
:origin => [self.origin, new_article.origin],
|
||||||
|
:unit => [self.unit, new_unit],
|
||||||
|
:price => [self.price.to_f.round(2), new_price.to_f.round(2)],
|
||||||
|
:tax => [self.tax, new_article.tax],
|
||||||
|
:deposit => [self.deposit.to_f.round(2), new_article.deposit.to_f.round(2)],
|
||||||
|
# take care of different num-objects.
|
||||||
|
:unit_quantity => [self.unit_quantity.to_s.to_f, new_unit_quantity.to_s.to_f],
|
||||||
|
:note => [self.note.to_s, new_article.note.to_s]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Compare attributes from two different articles.
|
||||||
|
#
|
||||||
|
# This is used for auto-synchronization
|
||||||
|
# @param attributes [Hash<Symbol, Array>] Attributes with old and new values
|
||||||
|
# @return [Hash<Symbol, Object>] Changed attributes with new values
|
||||||
def self.compare_attributes(attributes)
|
def self.compare_attributes(attributes)
|
||||||
unequal_attributes = attributes.select { |name, values| values[0] != values[1] && !(values[0].blank? && values[1].blank?) }
|
unequal_attributes = attributes.select { |name, values| values[0] != values[1] && !(values[0].blank? && values[1].blank?) }
|
||||||
unequal_attributes.collect { |pair| pair[0] }
|
Hash[unequal_attributes.to_a.map {|a| [a[0], a[1].last]}]
|
||||||
end
|
end
|
||||||
|
|
||||||
# to get the correspondent shared article
|
# to get the correspondent shared article
|
||||||
|
@ -157,25 +166,25 @@ class Article < ActiveRecord::Base
|
||||||
# returns new price and unit_quantity in array, when calc is possible => [price, unit_quanity]
|
# returns new price and unit_quantity in array, when calc is possible => [price, unit_quanity]
|
||||||
# returns false if units aren't foodsoft-compatible
|
# returns false if units aren't foodsoft-compatible
|
||||||
# returns nil if units are eqal
|
# returns nil if units are eqal
|
||||||
def convert_units
|
def convert_units(new_article = shared_article)
|
||||||
if unit != shared_article.unit
|
if unit != new_article.unit
|
||||||
# legacy, used by foodcoops in Germany
|
# legacy, used by foodcoops in Germany
|
||||||
if shared_article.unit == "KI" && unit == "ST" # 'KI' means a box, with a different amount of items in it
|
if new_article.unit == "KI" && unit == "ST" # 'KI' means a box, with a different amount of items in it
|
||||||
# try to match the size out of its name, e.g. "banana 10-12 St" => 10
|
# try to match the size out of its name, e.g. "banana 10-12 St" => 10
|
||||||
new_unit_quantity = /[0-9\-\s]+(St)/.match(shared_article.name).to_s.to_i
|
new_unit_quantity = /[0-9\-\s]+(St)/.match(new_article.name).to_s.to_i
|
||||||
if new_unit_quantity && new_unit_quantity > 0
|
if new_unit_quantity && new_unit_quantity > 0
|
||||||
new_price = (shared_article.price/new_unit_quantity.to_f).round(2)
|
new_price = (new_article.price/new_unit_quantity.to_f).round(2)
|
||||||
[new_price, new_unit_quantity]
|
[new_price, new_unit_quantity]
|
||||||
else
|
else
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
else # use ruby-units to convert
|
else # use ruby-units to convert
|
||||||
fc_unit = (::Unit.new(unit) rescue nil)
|
fc_unit = (::Unit.new(unit) rescue nil)
|
||||||
supplier_unit = (::Unit.new(shared_article.unit) rescue nil)
|
supplier_unit = (::Unit.new(new_article.unit) rescue nil)
|
||||||
if fc_unit && supplier_unit && fc_unit =~ supplier_unit
|
if fc_unit && supplier_unit && fc_unit =~ supplier_unit
|
||||||
conversion_factor = (supplier_unit / fc_unit).to_base.to_r
|
conversion_factor = (supplier_unit / fc_unit).to_base.to_r
|
||||||
new_price = shared_article.price / conversion_factor
|
new_price = new_article.price / conversion_factor
|
||||||
new_unit_quantity = shared_article.unit_quantity * conversion_factor
|
new_unit_quantity = new_article.unit_quantity * conversion_factor
|
||||||
[new_price, new_unit_quantity]
|
[new_price, new_unit_quantity]
|
||||||
else
|
else
|
||||||
false
|
false
|
||||||
|
|
|
@ -34,30 +34,9 @@ class Supplier < ActiveRecord::Base
|
||||||
shared_article = article.shared_article(self)
|
shared_article = article.shared_article(self)
|
||||||
|
|
||||||
if shared_article # article will be updated
|
if shared_article # article will be updated
|
||||||
|
|
||||||
unequal_attributes = article.shared_article_changed?(self)
|
unequal_attributes = article.shared_article_changed?(self)
|
||||||
unless unequal_attributes.blank? # skip if shared_article has not been changed
|
unless unequal_attributes.blank? # skip if shared_article has not been changed
|
||||||
|
article.attributes = unequal_attributes
|
||||||
# try to convert different units
|
|
||||||
new_price, new_unit_quantity = article.convert_units
|
|
||||||
if new_price && new_unit_quantity
|
|
||||||
article.price = new_price
|
|
||||||
article.unit_quantity = new_unit_quantity
|
|
||||||
else
|
|
||||||
article.price = shared_article.price
|
|
||||||
article.unit_quantity = shared_article.unit_quantity
|
|
||||||
article.unit = shared_article.unit
|
|
||||||
end
|
|
||||||
# update other attributes
|
|
||||||
article.attributes = {
|
|
||||||
:name => shared_article.name,
|
|
||||||
:manufacturer => shared_article.manufacturer,
|
|
||||||
:origin => shared_article.origin,
|
|
||||||
:shared_updated_on => shared_article.updated_on,
|
|
||||||
:tax => shared_article.tax,
|
|
||||||
:deposit => shared_article.deposit,
|
|
||||||
:note => shared_article.note
|
|
||||||
}
|
|
||||||
updated_articles << [article, unequal_attributes]
|
updated_articles << [article, unequal_attributes]
|
||||||
end
|
end
|
||||||
# Articles with no order number can be used to put non-shared articles
|
# Articles with no order number can be used to put non-shared articles
|
||||||
|
|
|
@ -14,13 +14,13 @@
|
||||||
.alert= t '.outlist.alert_used', article: article.name
|
.alert= t '.outlist.alert_used', article: article.name
|
||||||
%hr/
|
%hr/
|
||||||
|
|
||||||
- if @updated_articles.any?
|
- if @updated_article_pairs.any?
|
||||||
%h2= t '.update.title'
|
%h2= t '.update.title'
|
||||||
%p
|
%p
|
||||||
%i
|
%i
|
||||||
= t '.update.update_msg', count: @updated_articles.size
|
= t '.update.update_msg', count: @updated_article_pairs.size
|
||||||
= t '.update.body'
|
= t '.update.body'
|
||||||
= render 'sync_table', articles: @updated_articles, field: 'articles', hidden_fields: %w(shared_updated_on)
|
= render 'sync_table', articles: @updated_article_pairs, field: 'articles', hidden_fields: %w(shared_updated_on)
|
||||||
%hr/
|
%hr/
|
||||||
|
|
||||||
- if @new_articles.any?
|
- if @new_articles.any?
|
||||||
|
|
|
@ -1,20 +1,25 @@
|
||||||
# Module for Foodsoft-File import
|
require 'roo'
|
||||||
# The Foodsoft-File is a cvs-file, with columns separated by semicolons
|
|
||||||
|
|
||||||
require 'csv'
|
# Foodsoft-file import
|
||||||
|
class FoodsoftFile
|
||||||
module FoodsoftFile
|
|
||||||
|
|
||||||
# parses a string from a foodsoft-file
|
# parses a string from a foodsoft-file
|
||||||
# returns two arrays with articles and outlisted_articles
|
# returns two arrays with articles and outlisted_articles
|
||||||
# the parsed article is a simple hash
|
# the parsed article is a simple hash
|
||||||
def self.parse(file)
|
def self.parse(file, options = {})
|
||||||
articles, outlisted_articles = Array.new, Array.new
|
filepath = file.is_a?(String) ? file : file.to_path
|
||||||
row_index = 2
|
filename = options[:filename] || filepath
|
||||||
::CSV.parse(file.read.force_encoding('utf-8'), {:col_sep => ";", :headers => true}) do |row|
|
fileext = ::File.extname(filename)
|
||||||
# check if the line is empty
|
options = {col_sep: ';', encoding: 'utf-8'}.merge(options)
|
||||||
unless row[2] == "" || row[2].nil?
|
s = Roo::Spreadsheet.open filepath, extension: fileext, csv_options: options
|
||||||
article = {:number => row[1],
|
|
||||||
|
row_index = 1
|
||||||
|
s.each do |row|
|
||||||
|
if row_index == 1
|
||||||
|
# @todo try to detect headers; for now using the index is ok
|
||||||
|
|
||||||
|
elsif !row[2].blank?
|
||||||
|
article = {:order_number => row[1],
|
||||||
:name => row[2],
|
:name => row[2],
|
||||||
:note => row[3],
|
:note => row[3],
|
||||||
:manufacturer => row[4],
|
:manufacturer => row[4],
|
||||||
|
@ -24,20 +29,13 @@ module FoodsoftFile
|
||||||
:tax => row[8],
|
:tax => row[8],
|
||||||
:deposit => (row[9].nil? ? "0" : row[9]),
|
:deposit => (row[9].nil? ? "0" : row[9]),
|
||||||
:unit_quantity => row[10],
|
:unit_quantity => row[10],
|
||||||
:scale_quantity => row[11],
|
:article_category => row[13]}
|
||||||
:scale_price => row[12],
|
status = row[0] && row[0].strip.downcase == 'x' ? :outlisted : nil
|
||||||
:category => row[13]}
|
yield status, article, row_index
|
||||||
case row[0]
|
|
||||||
when "x"
|
|
||||||
# check if the article is outlisted
|
|
||||||
outlisted_articles << article
|
|
||||||
else
|
|
||||||
articles << article
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
row_index += 1
|
row_index += 1
|
||||||
end
|
end
|
||||||
return [articles, outlisted_articles]
|
row_index
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue