2012-04-16 08:48:01 +02:00
|
|
|
# encoding: utf-8
|
2009-01-06 11:49:19 +01:00
|
|
|
class Article < ActiveRecord::Base
|
2009-01-29 01:57:51 +01:00
|
|
|
extend ActiveSupport::Memoizable # Ability to cache method results. Use memoize :expensive_method
|
2009-01-20 19:37:15 +01:00
|
|
|
|
2011-05-20 00:19:58 +02:00
|
|
|
# Replace numeric seperator with database format
|
|
|
|
localize_input_of :price, :tax, :deposit
|
|
|
|
|
2009-01-29 01:57:51 +01:00
|
|
|
# Associations
|
2013-03-16 17:53:24 +01:00
|
|
|
belongs_to :supplier
|
2009-01-06 11:49:19 +01:00
|
|
|
belongs_to :article_category
|
2009-05-13 12:52:35 +02:00
|
|
|
has_many :article_prices, :order => "created_at DESC"
|
2009-01-18 17:42:51 +01:00
|
|
|
|
2013-03-16 17:53:24 +01:00
|
|
|
scope :undeleted, -> { where(deleted_at: nil) }
|
2013-03-17 18:36:17 +01:00
|
|
|
scope :available, -> { undeleted.where(availability: true) }
|
2011-05-11 12:21:21 +02:00
|
|
|
scope :not_in_stock, :conditions => {:type => nil}
|
2009-01-29 01:57:51 +01:00
|
|
|
|
|
|
|
# Validations
|
2012-10-28 18:03:50 +01:00
|
|
|
validates_presence_of :name, :unit, :price, :tax, :deposit, :unit_quantity, :supplier_id, :article_category
|
2009-01-06 11:49:19 +01:00
|
|
|
validates_length_of :name, :in => 4..60
|
|
|
|
validates_length_of :unit, :in => 2..15
|
2009-07-21 15:09:55 +02:00
|
|
|
validates_numericality_of :price, :unit_quantity, :greater_than => 0
|
2009-01-06 11:49:19 +01:00
|
|
|
validates_numericality_of :deposit, :tax
|
2009-10-12 14:42:31 +02:00
|
|
|
validates_uniqueness_of :name, :scope => [:supplier_id, :deleted_at, :type]
|
2009-01-06 11:49:19 +01:00
|
|
|
|
2009-01-29 01:57:51 +01:00
|
|
|
# Callbacks
|
|
|
|
before_save :update_price_history
|
|
|
|
before_destroy :check_article_in_use
|
2009-01-06 11:49:19 +01:00
|
|
|
|
2009-01-29 01:57:51 +01:00
|
|
|
# The financial gross, net plus tax and deposti
|
|
|
|
def gross_price
|
|
|
|
((price + deposit) * (tax / 100 + 1)).round(2)
|
|
|
|
end
|
|
|
|
|
|
|
|
# The price for the foodcoop-member.
|
|
|
|
def fc_price
|
2012-08-24 19:52:38 +02:00
|
|
|
(gross_price * (FoodsoftConfig[:price_markup] / 100 + 1)).round(2)
|
2009-01-06 11:49:19 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
# Returns true if article has been updated at least 2 days ago
|
|
|
|
def recently_updated
|
|
|
|
updated_at > 2.days.ago
|
|
|
|
end
|
|
|
|
|
2009-01-29 01:57:51 +01:00
|
|
|
# If the article is used in an open Order, the Order will be returned.
|
|
|
|
def in_open_order
|
2009-02-03 21:14:48 +01:00
|
|
|
order_articles = OrderArticle.all(:conditions => ['order_id IN (?)', Order.open.collect(&:id)])
|
2009-01-29 01:57:51 +01:00
|
|
|
order_article = order_articles.detect {|oa| oa.article_id == id }
|
|
|
|
order_article ? order_article.order : nil
|
2009-01-06 11:49:19 +01:00
|
|
|
end
|
2009-01-29 01:57:51 +01:00
|
|
|
memoize :in_open_order
|
2009-01-06 11:49:19 +01:00
|
|
|
|
|
|
|
# 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,
|
|
|
|
# false will returned and self.shared_updated_on will be updated
|
|
|
|
def shared_article_changed?
|
|
|
|
# skip early if the timestamp hasn't changed
|
|
|
|
unless self.shared_updated_on == self.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 and new_unit_quantity
|
|
|
|
# if convertion isn't possible, take shared_article-price/unit_quantity
|
|
|
|
new_price, new_unit_quantity, new_unit = self.shared_article.price, self.shared_article.unit_quantity, self.shared_article.unit
|
|
|
|
end
|
|
|
|
|
|
|
|
# check if all attributes differ
|
|
|
|
unequal_attributes = Article.compare_attributes(
|
|
|
|
{
|
|
|
|
:name => [self.name, self.shared_article.name],
|
|
|
|
:manufacturer => [self.manufacturer, self.shared_article.manufacturer.to_s],
|
|
|
|
:origin => [self.origin, self.shared_article.origin],
|
|
|
|
:unit => [self.unit, new_unit],
|
2009-01-29 01:57:51 +01:00
|
|
|
:price => [self.price, new_price],
|
2009-01-06 11:49:19 +01:00
|
|
|
:tax => [self.tax, self.shared_article.tax],
|
|
|
|
:deposit => [self.deposit, self.shared_article.deposit],
|
|
|
|
# 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, self.shared_article.note.to_s]
|
|
|
|
}
|
|
|
|
)
|
|
|
|
if unequal_attributes.empty?
|
|
|
|
# when attributes not changed, update timestamp of article
|
|
|
|
self.update_attribute(:shared_updated_on, self.shared_article.updated_on)
|
|
|
|
false
|
|
|
|
else
|
|
|
|
unequal_attributes
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# compare attributes from different articles. used for auto-synchronization
|
|
|
|
# returns array of symbolized unequal attributes
|
|
|
|
def self.compare_attributes(attributes)
|
|
|
|
unequal_attributes = attributes.select { |name, values| values[0] != values[1] }
|
|
|
|
unequal_attributes.collect { |pair| pair[0] }
|
|
|
|
end
|
|
|
|
|
|
|
|
# to get the correspondent shared article
|
|
|
|
def shared_article
|
|
|
|
@shared_article ||= self.supplier.shared_supplier.shared_articles.find_by_number(self.order_number)
|
|
|
|
end
|
|
|
|
|
|
|
|
# convert units in foodcoop-size
|
2009-01-06 15:45:19 +01:00
|
|
|
# uses unit factors in app_config.yml to calc the price/unit_quantity
|
2009-01-06 11:49:19 +01:00
|
|
|
# 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
|
|
|
|
if shared_article.unit == "KI" and 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
|
|
|
|
if new_unit_quantity and new_unit_quantity > 0
|
|
|
|
new_price = (shared_article.price/new_unit_quantity.to_f).round(2)
|
|
|
|
[new_price, new_unit_quantity]
|
|
|
|
else
|
|
|
|
false
|
|
|
|
end
|
|
|
|
else # get factors for fc and supplier
|
2012-08-24 19:52:38 +02:00
|
|
|
fc_unit_factor = FoodsoftConfig[:units][self.unit]
|
|
|
|
supplier_unit_factor = FoodsoftConfig[:units][self.shared_article.unit]
|
2009-01-06 11:49:19 +01:00
|
|
|
if fc_unit_factor and supplier_unit_factor
|
|
|
|
convertion_factor = fc_unit_factor / supplier_unit_factor
|
|
|
|
new_price = BigDecimal((convertion_factor * shared_article.price).to_s).round(2)
|
|
|
|
new_unit_quantity = ( 1 / convertion_factor) * shared_article.unit_quantity
|
|
|
|
[new_price, new_unit_quantity]
|
|
|
|
else
|
|
|
|
false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
else
|
|
|
|
nil
|
|
|
|
end
|
|
|
|
end
|
2009-01-16 02:17:49 +01:00
|
|
|
|
2013-03-16 17:53:24 +01:00
|
|
|
def deleted?
|
|
|
|
deleted_at.present?
|
|
|
|
end
|
|
|
|
|
|
|
|
def mark_as_deleted
|
|
|
|
check_article_in_use
|
|
|
|
update_column :deleted_at, Time.now
|
|
|
|
end
|
|
|
|
|
2009-01-29 01:57:51 +01:00
|
|
|
protected
|
|
|
|
|
|
|
|
# Checks if the article is in use before it will deleted
|
|
|
|
def check_article_in_use
|
2009-02-11 15:23:59 +01:00
|
|
|
raise self.name.to_s + " kann nicht gelöscht werden. Der Artikel befindet sich in einer laufenden Bestellung!" if self.in_open_order
|
2009-01-29 01:57:51 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
# Create an ArticlePrice, when the price-attr are changed.
|
|
|
|
def update_price_history
|
|
|
|
if price_changed?
|
2009-02-04 16:41:01 +01:00
|
|
|
article_prices.build(
|
2009-01-29 01:57:51 +01:00
|
|
|
:price => price,
|
|
|
|
:tax => tax,
|
|
|
|
:deposit => deposit,
|
|
|
|
:unit_quantity => unit_quantity
|
|
|
|
)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def price_changed?
|
|
|
|
changed.detect { |attr| attr == 'price' || 'tax' || 'deposit' || 'unit_quantity' } ? true : false
|
|
|
|
end
|
2009-01-06 11:49:19 +01:00
|
|
|
end
|