2009-01-06 11:49:19 +01:00
|
|
|
class Order < ActiveRecord::Base
|
2009-01-29 01:57:51 +01:00
|
|
|
extend ActiveSupport::Memoizable # Ability to cache method results. Use memoize :expensive_method
|
|
|
|
acts_as_ordered :order => "ends" # easyier find of next or previous model
|
|
|
|
|
|
|
|
# Associations
|
2009-01-06 11:49:19 +01:00
|
|
|
has_many :order_articles, :dependent => :destroy
|
|
|
|
has_many :articles, :through => :order_articles
|
|
|
|
has_many :group_orders, :dependent => :destroy
|
2009-01-14 12:46:01 +01:00
|
|
|
has_many :ordergroups, :through => :group_orders
|
2009-01-29 01:57:51 +01:00
|
|
|
has_one :invoice
|
|
|
|
has_many :comments, :class_name => "OrderComment", :order => "created_at"
|
2009-02-06 16:26:35 +01:00
|
|
|
has_many :stock_changes
|
2009-01-06 11:49:19 +01:00
|
|
|
belongs_to :supplier
|
2012-11-12 14:24:49 +01:00
|
|
|
belongs_to :updated_by, :class_name => 'User', :foreign_key => 'updated_by_user_id'
|
|
|
|
belongs_to :created_by, :class_name => 'User', :foreign_key => 'created_by_user_id'
|
2009-01-06 11:49:19 +01:00
|
|
|
|
2009-01-29 01:57:51 +01:00
|
|
|
# Validations
|
2009-02-05 16:40:02 +01:00
|
|
|
validates_presence_of :starts
|
|
|
|
validate :starts_before_ends, :include_articles
|
2009-01-10 22:22:16 +01:00
|
|
|
|
2009-01-29 01:57:51 +01:00
|
|
|
# Callbacks
|
2012-11-12 14:24:49 +01:00
|
|
|
before_create do |order|
|
|
|
|
order.created_by = User.current_user
|
|
|
|
end
|
2009-01-29 01:57:51 +01:00
|
|
|
after_update :update_price_of_group_orders
|
|
|
|
|
|
|
|
# Finders
|
2009-02-05 16:40:02 +01:00
|
|
|
named_scope :open, :conditions => {:state => 'open'}, :order => 'ends DESC'
|
|
|
|
named_scope :finished, :conditions => "state = 'finished' OR state = 'closed'", :order => 'ends DESC'
|
|
|
|
named_scope :finished_not_closed, :conditions => {:state => 'finished'}, :order => 'ends DESC'
|
|
|
|
named_scope :closed, :conditions => {:state => 'closed'}, :order => 'ends DESC'
|
|
|
|
named_scope :stockit, :conditions => {:supplier_id => 0}, :order => 'ends DESC'
|
|
|
|
|
|
|
|
def stockit?
|
|
|
|
supplier_id == 0
|
|
|
|
end
|
|
|
|
|
|
|
|
def name
|
|
|
|
stockit? ? "Lager" : supplier.name
|
|
|
|
end
|
|
|
|
|
|
|
|
def articles_for_ordering
|
2010-02-09 21:45:57 +01:00
|
|
|
if stockit?
|
|
|
|
StockArticle.available.without_deleted(:include => :article_category,
|
|
|
|
:order => 'article_categories.name, articles.name').reject{ |a|
|
|
|
|
a.quantity_available <= 0
|
|
|
|
}.group_by { |a| a.article_category.name }
|
|
|
|
else
|
|
|
|
supplier.articles.available.without_deleted.group_by { |a| a.article_category.name }
|
|
|
|
end
|
2009-02-05 16:40:02 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
# Fetch last orders from same supplier, to generate an article selection proposal
|
|
|
|
def templates
|
|
|
|
if stockit?
|
|
|
|
Order.stockit :limit => 5
|
|
|
|
else
|
|
|
|
supplier.orders.finished :limit => 5
|
|
|
|
end
|
|
|
|
end
|
2009-01-10 22:22:16 +01:00
|
|
|
|
2009-01-06 11:49:19 +01:00
|
|
|
# Create or destroy OrderArticle associations on create/update
|
|
|
|
def article_ids=(ids)
|
|
|
|
# fetch selected articles
|
|
|
|
articles_list = Article.find(ids)
|
|
|
|
# create new order_articles
|
|
|
|
(articles_list - articles).each { |article| order_articles.build(:article => article) }
|
|
|
|
# delete old order_articles
|
|
|
|
articles.reject { |article| articles_list.include?(article) }.each do |article|
|
|
|
|
order_articles.detect { |order_article| order_article.article_id == article.id }.destroy
|
|
|
|
end
|
|
|
|
end
|
2009-01-29 01:57:51 +01:00
|
|
|
|
|
|
|
def open?
|
|
|
|
state == "open"
|
2009-01-06 11:49:19 +01:00
|
|
|
end
|
2009-01-29 01:57:51 +01:00
|
|
|
|
|
|
|
def finished?
|
|
|
|
state == "finished"
|
2009-01-06 11:49:19 +01:00
|
|
|
end
|
2009-01-29 01:57:51 +01:00
|
|
|
|
|
|
|
def closed?
|
|
|
|
state == "closed"
|
2009-01-06 11:49:19 +01:00
|
|
|
end
|
|
|
|
|
2010-06-19 12:23:18 +02:00
|
|
|
def expired?
|
|
|
|
!ends.nil? && ends < Time.now
|
|
|
|
end
|
|
|
|
|
2009-01-14 12:46:01 +01:00
|
|
|
# search GroupOrder of given Ordergroup
|
2009-01-06 11:49:19 +01:00
|
|
|
def group_order(ordergroup)
|
2009-01-29 01:57:51 +01:00
|
|
|
group_orders.first :conditions => { :ordergroup_id => ordergroup.id }
|
2009-01-06 11:49:19 +01:00
|
|
|
end
|
2009-02-06 16:26:35 +01:00
|
|
|
|
2009-01-06 11:49:19 +01:00
|
|
|
# Returns OrderArticles in a nested Array, grouped by category and ordered by article name.
|
|
|
|
# The array has the following form:
|
|
|
|
# e.g: [["drugs",[teethpaste, toiletpaper]], ["fruits" => [apple, banana, lemon]]]
|
2009-02-04 16:41:01 +01:00
|
|
|
def articles_grouped_by_category
|
2009-01-29 01:57:51 +01:00
|
|
|
order_articles.all(:include => [:article, :article_price], :order => 'articles.name').group_by { |a|
|
|
|
|
a.article.article_category.name
|
|
|
|
}.sort { |a, b| a[0] <=> b[0] }
|
2009-01-06 11:49:19 +01:00
|
|
|
end
|
2009-02-04 16:41:01 +01:00
|
|
|
memoize :articles_grouped_by_category
|
|
|
|
|
|
|
|
def articles_sort_by_category
|
|
|
|
order_articles.all(:include => [:article], :order => 'articles.name').sort do |a,b|
|
|
|
|
a.article.article_category.name <=> b.article.article_category.name
|
|
|
|
end
|
|
|
|
end
|
2009-01-06 11:49:19 +01:00
|
|
|
|
|
|
|
# Returns the defecit/benefit for the foodcoop
|
2009-01-29 21:28:22 +01:00
|
|
|
# Requires a valid invoice, belonging to this order
|
2009-03-17 19:43:41 +01:00
|
|
|
#FIXME: Consider order.foodcoop_result
|
2009-01-29 21:28:22 +01:00
|
|
|
def profit(options = {})
|
2009-03-17 19:43:41 +01:00
|
|
|
markup = options[:without_markup] || false
|
2009-01-29 21:28:22 +01:00
|
|
|
if invoice
|
2009-03-17 19:43:41 +01:00
|
|
|
groups_sum = markup ? sum(:groups_without_markup) : sum(:groups)
|
2009-01-29 21:28:22 +01:00
|
|
|
groups_sum - invoice.net_amount
|
|
|
|
end
|
2009-01-06 11:49:19 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
# Returns the all round price of a finished order
|
2009-01-29 01:57:51 +01:00
|
|
|
# :groups returns the sum of all GroupOrders
|
|
|
|
# :clear returns the price without tax, deposit and markup
|
|
|
|
# :gross includes tax and deposit. this amount should be equal to suppliers bill
|
|
|
|
# :fc, guess what...
|
|
|
|
def sum(type = :gross)
|
|
|
|
total = 0
|
2009-02-03 21:14:48 +01:00
|
|
|
if type == :net || type == :gross || type == :fc
|
2009-01-29 01:57:51 +01:00
|
|
|
for oa in order_articles.ordered.all(:include => [:article,:article_price])
|
|
|
|
quantity = oa.units_to_order * oa.price.unit_quantity
|
|
|
|
case type
|
2009-02-03 21:14:48 +01:00
|
|
|
when :net
|
2009-01-29 01:57:51 +01:00
|
|
|
total += quantity * oa.price.price
|
|
|
|
when :gross
|
|
|
|
total += quantity * oa.price.gross_price
|
|
|
|
when :fc
|
|
|
|
total += quantity * oa.price.fc_price
|
2009-01-06 11:49:19 +01:00
|
|
|
end
|
2009-01-29 01:57:51 +01:00
|
|
|
end
|
|
|
|
elsif type == :groups || type == :groups_without_markup
|
2009-03-17 19:43:41 +01:00
|
|
|
for go in group_orders.all(:include => :group_order_articles)
|
|
|
|
for goa in go.group_order_articles.all(:include => [:order_article])
|
2009-01-06 11:49:19 +01:00
|
|
|
case type
|
2009-01-29 01:57:51 +01:00
|
|
|
when :groups
|
2009-02-04 16:41:01 +01:00
|
|
|
total += goa.result * goa.order_article.price.fc_price
|
2009-01-29 01:57:51 +01:00
|
|
|
when :groups_without_markup
|
2009-02-04 16:41:01 +01:00
|
|
|
total += goa.result * goa.order_article.price.gross_price
|
2009-01-06 11:49:19 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2009-01-29 01:57:51 +01:00
|
|
|
total
|
2009-01-06 11:49:19 +01:00
|
|
|
end
|
|
|
|
|
2009-01-29 01:57:51 +01:00
|
|
|
# Finishes this order. This will set the order state to "finish" and the end property to the current time.
|
2009-01-06 11:49:19 +01:00
|
|
|
# Ignored if the order is already finished.
|
2009-01-29 01:57:51 +01:00
|
|
|
def finish!(user)
|
|
|
|
unless finished?
|
|
|
|
Order.transaction do
|
|
|
|
# Update order_articles. Save the current article_price to keep price consistency
|
2009-02-03 21:14:48 +01:00
|
|
|
# Also save results for each group_order_result
|
2009-01-29 01:57:51 +01:00
|
|
|
order_articles.all(:include => :article).each do |oa|
|
|
|
|
oa.update_attribute(:article_price, oa.article.article_prices.first)
|
2009-02-03 21:14:48 +01:00
|
|
|
oa.group_order_articles.each { |goa| goa.save_results! }
|
2009-01-06 11:49:19 +01:00
|
|
|
end
|
2009-03-17 10:47:00 +01:00
|
|
|
|
2009-01-29 01:57:51 +01:00
|
|
|
# set new order state (needed by notify_order_finished)
|
|
|
|
update_attributes(:state => 'finished', :ends => Time.now, :updated_by => user)
|
|
|
|
end
|
2009-01-06 11:49:19 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2009-01-30 22:27:55 +01:00
|
|
|
# Sets order.status to 'close' and updates all Ordergroup.account_balances
|
|
|
|
def close!(user)
|
|
|
|
raise "Bestellung wurde schon abgerechnet" if closed?
|
2009-02-05 16:40:02 +01:00
|
|
|
transaction_note = "Bestellung: #{name}, bis #{ends.strftime('%d.%m.%Y')}"
|
2009-01-30 22:27:55 +01:00
|
|
|
|
|
|
|
gos = group_orders.all(:include => :ordergroup) # Fetch group_orders
|
|
|
|
gos.each { |group_order| group_order.update_price! } # Update prices of group_orders
|
|
|
|
|
|
|
|
transaction do # Start updating account balances
|
|
|
|
for group_order in gos
|
|
|
|
price = group_order.price * -1 # decrease! account balance
|
2009-02-04 16:41:01 +01:00
|
|
|
group_order.ordergroup.add_financial_transaction(price, transaction_note, user)
|
2009-01-06 11:49:19 +01:00
|
|
|
end
|
2009-02-06 16:26:35 +01:00
|
|
|
|
|
|
|
if stockit? # Decreases the quantity of stock_articles
|
|
|
|
for oa in order_articles.all(:include => :article)
|
|
|
|
oa.update_results! # Update units_to_order of order_article
|
|
|
|
stock_changes.create! :stock_article => oa.article, :quantity => oa.units_to_order*-1
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2009-03-17 19:43:41 +01:00
|
|
|
self.update_attributes! :state => 'closed', :updated_by => user, :foodcoop_result => profit
|
2009-01-06 11:49:19 +01:00
|
|
|
end
|
|
|
|
end
|
2009-03-11 16:58:31 +01:00
|
|
|
|
2009-01-06 11:49:19 +01:00
|
|
|
protected
|
|
|
|
|
2009-01-29 01:57:51 +01:00
|
|
|
def starts_before_ends
|
2010-02-09 21:45:57 +01:00
|
|
|
errors.add(:ends, "muss nach dem Bestellstart liegen (oder leer bleiben)") if (ends && starts && ends <= starts)
|
2009-01-29 01:57:51 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
def include_articles
|
|
|
|
errors.add(:order_articles, "Es muss mindestens ein Artikel ausgewählt sein") if order_articles.empty?
|
|
|
|
end
|
2009-01-06 11:49:19 +01:00
|
|
|
|
|
|
|
private
|
|
|
|
|
2009-01-29 01:57:51 +01:00
|
|
|
# Updates the "price" attribute of GroupOrders or GroupOrderResults
|
|
|
|
# This will be either the maximum value of a current order or the actual order value of a finished order.
|
|
|
|
def update_price_of_group_orders
|
|
|
|
group_orders.each { |group_order| group_order.update_price! }
|
|
|
|
end
|
|
|
|
|
2009-01-06 11:49:19 +01:00
|
|
|
end
|
2011-05-07 21:55:24 +02:00
|
|
|
|
|
|
|
# == Schema Information
|
|
|
|
#
|
|
|
|
# Table name: orders
|
|
|
|
#
|
|
|
|
# id :integer(4) not null, primary key
|
|
|
|
# supplier_id :integer(4)
|
|
|
|
# note :text
|
|
|
|
# starts :datetime
|
|
|
|
# ends :datetime
|
|
|
|
# state :string(255) default("open")
|
|
|
|
# lock_version :integer(4) default(0), not null
|
|
|
|
# updated_by_user_id :integer(4)
|
|
|
|
# foodcoop_result :decimal(8, 2)
|
|
|
|
#
|
|
|
|
|