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
|
|
@ -62,11 +62,11 @@ class Article < ActiveRecord::Base
|
|||
#validates_uniqueness_of :name, :scope => [:supplier_id, :deleted_at, :type], if: Proc.new {|a| a.supplier.shared_sync_method.blank? or a.supplier.shared_sync_method == 'import' }
|
||||
#validates_uniqueness_of :name, :scope => [:supplier_id, :deleted_at, :type, :unit, :unit_quantity]
|
||||
validate :uniqueness_of_name
|
||||
|
||||
|
||||
# Callbacks
|
||||
before_save :update_price_history
|
||||
before_destroy :check_article_in_use
|
||||
|
||||
|
||||
# The financial gross, net plus tax and deposti
|
||||
def gross_price
|
||||
((price + deposit) * (tax / 100 + 1)).round(2)
|
||||
|
|
@ -76,12 +76,12 @@ class Article < ActiveRecord::Base
|
|||
def fc_price
|
||||
(gross_price * (FoodsoftConfig[:price_markup] / 100 + 1)).round(2)
|
||||
end
|
||||
|
||||
|
||||
# Returns true if article has been updated at least 2 days ago
|
||||
def recently_updated
|
||||
updated_at > 2.days.ago
|
||||
end
|
||||
|
||||
|
||||
# If the article is used in an open Order, the Order will be returned.
|
||||
def in_open_order
|
||||
@in_open_order ||= begin
|
||||
|
|
@ -90,92 +90,101 @@ class Article < ActiveRecord::Base
|
|||
order_article ? order_article.order : nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Returns true if the article has been ordered in the given order at least once
|
||||
def ordered_in_order?(order)
|
||||
order.order_articles.where(article_id: id).where('quantity > 0').one?
|
||||
end
|
||||
|
||||
|
||||
# this method checks, if the shared_article has been changed
|
||||
# unequal attributes will returned in array
|
||||
# if only the timestamps differ and the attributes are equal,
|
||||
# if only the timestamps differ and the attributes are equal,
|
||||
# false will returned and self.shared_updated_on will be updated
|
||||
def shared_article_changed?(supplier = self.supplier)
|
||||
# skip early if the timestamp hasn't changed
|
||||
shared_article = self.shared_article(supplier)
|
||||
unless shared_article.nil? || self.shared_updated_on == shared_article.updated_on
|
||||
|
||||
# try to convert units
|
||||
# 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?
|
||||
attrs = unequal_attributes(shared_article)
|
||||
if attrs.empty?
|
||||
# when attributes not changed, update timestamp of article
|
||||
self.update_attribute(:shared_updated_on, shared_article.updated_on)
|
||||
false
|
||||
else
|
||||
unequal_attributes
|
||||
attrs
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# compare attributes from different articles. used for auto-synchronization
|
||||
# returns array of symbolized unequal attributes
|
||||
|
||||
# Return article attributes that were changed (incl. unit conversion)
|
||||
# @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)
|
||||
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
|
||||
|
||||
|
||||
# to get the correspondent shared article
|
||||
def shared_article(supplier = self.supplier)
|
||||
self.order_number.blank? and return nil
|
||||
@shared_article ||= supplier.shared_supplier.shared_articles.find_by_number(self.order_number) rescue nil
|
||||
end
|
||||
|
||||
|
||||
# convert units in foodcoop-size
|
||||
# uses unit factors in app_config.yml to calc the price/unit_quantity
|
||||
# returns new price and unit_quantity in array, when calc is possible => [price, unit_quanity]
|
||||
# returns false if units aren't foodsoft-compatible
|
||||
# returns nil if units are eqal
|
||||
def convert_units
|
||||
if unit != shared_article.unit
|
||||
def convert_units(new_article = shared_article)
|
||||
if unit != new_article.unit
|
||||
# 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
|
||||
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
|
||||
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]
|
||||
else
|
||||
false
|
||||
end
|
||||
else # use ruby-units to convert
|
||||
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
|
||||
conversion_factor = (supplier_unit / fc_unit).to_base.to_r
|
||||
new_price = shared_article.price / conversion_factor
|
||||
new_unit_quantity = shared_article.unit_quantity * conversion_factor
|
||||
new_price = new_article.price / conversion_factor
|
||||
new_unit_quantity = new_article.unit_quantity * conversion_factor
|
||||
[new_price, new_unit_quantity]
|
||||
else
|
||||
false
|
||||
|
|
@ -196,7 +205,7 @@ class Article < ActiveRecord::Base
|
|||
end
|
||||
|
||||
protected
|
||||
|
||||
|
||||
# Checks if the article is in use before it will deleted
|
||||
def check_article_in_use
|
||||
raise I18n.t('articles.model.error_in_use', :article => self.name.to_s) if self.in_open_order
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue