1298af8377
Attention, every delayed job has to know its Foodcoop scope. Otherwise every task will be executed in the default database. See email delivery.
232 lines
7.7 KiB
Ruby
232 lines
7.7 KiB
Ruby
# encoding: utf-8
|
|
#
|
|
class Order < ActiveRecord::Base
|
|
|
|
# Associations
|
|
has_many :order_articles, :dependent => :destroy
|
|
has_many :articles, :through => :order_articles
|
|
has_many :group_orders, :dependent => :destroy
|
|
has_many :ordergroups, :through => :group_orders
|
|
has_one :invoice
|
|
has_many :comments, :class_name => "OrderComment", :order => "created_at"
|
|
has_many :stock_changes
|
|
belongs_to :supplier
|
|
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'
|
|
|
|
# Validations
|
|
validates_presence_of :starts
|
|
validate :starts_before_ends, :include_articles
|
|
|
|
# Callbacks
|
|
before_create do |order|
|
|
order.created_by = User.current_user
|
|
end
|
|
after_update :update_price_of_group_orders
|
|
after_save :save_order_articles
|
|
|
|
# Finders
|
|
scope :open, where(state: 'open').order('ends DESC')
|
|
scope :finished, where("orders.state = 'finished' OR orders.state = 'closed'").order('ends DESC')
|
|
scope :finished_not_closed, where(state: 'finished').order('ends DESC')
|
|
scope :closed, where(state: 'closed').order('ends DESC')
|
|
scope :stockit, where(supplier_id: 0).order('ends DESC')
|
|
|
|
def stockit?
|
|
supplier_id == 0
|
|
end
|
|
|
|
def name
|
|
stockit? ? "Lager" : supplier.name
|
|
end
|
|
|
|
def articles_for_ordering
|
|
if stockit?
|
|
StockArticle.available.all(: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.all.group_by { |a| a.article_category.name }
|
|
end
|
|
end
|
|
|
|
# Save ids, and create/delete order_articles after successfully saved the order
|
|
def article_ids=(ids)
|
|
@article_ids = ids
|
|
end
|
|
|
|
def article_ids
|
|
@article_ids ||= order_articles.map(&:article_id)
|
|
end
|
|
|
|
def open?
|
|
state == "open"
|
|
end
|
|
|
|
def finished?
|
|
state == "finished"
|
|
end
|
|
|
|
def closed?
|
|
state == "closed"
|
|
end
|
|
|
|
def expired?
|
|
!ends.nil? && ends < Time.now
|
|
end
|
|
|
|
# search GroupOrder of given Ordergroup
|
|
def group_order(ordergroup)
|
|
group_orders.where(:ordergroup_id => ordergroup.id).first
|
|
end
|
|
|
|
# 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]]]
|
|
def articles_grouped_by_category
|
|
order_articles.includes([:article_price, :group_order_articles, :article => :article_category]).
|
|
order('articles.name').
|
|
group_by { |a| a.article.article_category.name }.
|
|
sort { |a, b| a[0] <=> b[0] }
|
|
end
|
|
|
|
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
|
|
|
|
# Returns the defecit/benefit for the foodcoop
|
|
# Requires a valid invoice, belonging to this order
|
|
#FIXME: Consider order.foodcoop_result
|
|
def profit(options = {})
|
|
markup = options[:without_markup] || false
|
|
if invoice
|
|
groups_sum = markup ? sum(:groups_without_markup) : sum(:groups)
|
|
groups_sum - invoice.net_amount
|
|
end
|
|
end
|
|
|
|
# Returns the all round price of a finished order
|
|
# :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
|
|
if type == :net || type == :gross || type == :fc
|
|
for oa in order_articles.ordered.all(:include => [:article,:article_price])
|
|
quantity = oa.units_to_order * oa.price.unit_quantity
|
|
case type
|
|
when :net
|
|
total += quantity * oa.price.price
|
|
when :gross
|
|
total += quantity * oa.price.gross_price
|
|
when :fc
|
|
total += quantity * oa.price.fc_price
|
|
end
|
|
end
|
|
elsif type == :groups || type == :groups_without_markup
|
|
for go in group_orders.all(:include => :group_order_articles)
|
|
for goa in go.group_order_articles.all(:include => [:order_article])
|
|
case type
|
|
when :groups
|
|
total += goa.result * goa.order_article.price.fc_price
|
|
when :groups_without_markup
|
|
total += goa.result * goa.order_article.price.gross_price
|
|
end
|
|
end
|
|
end
|
|
end
|
|
total
|
|
end
|
|
|
|
# Finishes this order. This will set the order state to "finish" and the end property to the current time.
|
|
# Ignored if the order is already finished.
|
|
def finish!(user)
|
|
unless finished?
|
|
Order.transaction do
|
|
# set new order state (needed by notify_order_finished)
|
|
update_attributes(:state => 'finished', :ends => Time.now, :updated_by => user)
|
|
|
|
# Update order_articles. Save the current article_price to keep price consistency
|
|
# Also save results for each group_order_result
|
|
# Clean up
|
|
order_articles.all(:include => :article).each do |oa|
|
|
oa.update_attribute(:article_price, oa.article.article_prices.first)
|
|
oa.group_order_articles.each do |goa|
|
|
goa.save_results!
|
|
# Delete no longer required order-history (group_order_article_quantities) and
|
|
# TODO: Do we need articles, which aren't ordered? (units_to_order == 0 ?)
|
|
goa.group_order_article_quantities.clear
|
|
end
|
|
end
|
|
|
|
# Update GroupOrder prices
|
|
group_orders.each(&:update_price!)
|
|
|
|
# Stats
|
|
ordergroups.each(&:update_stats!)
|
|
|
|
# Notifications
|
|
UserNotifier.delay.finished_order(self.id)
|
|
end
|
|
end
|
|
end
|
|
|
|
# Sets order.status to 'close' and updates all Ordergroup.account_balances
|
|
def close!(user)
|
|
raise "Bestellung wurde schon abgerechnet" if closed?
|
|
transaction_note = "Bestellung: #{name}, bis #{ends.strftime('%d.%m.%Y')}"
|
|
|
|
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
|
|
group_order.ordergroup.add_financial_transaction!(price, transaction_note, user)
|
|
end
|
|
|
|
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
|
|
|
|
self.update_attributes! :state => 'closed', :updated_by => user, :foodcoop_result => profit
|
|
end
|
|
end
|
|
|
|
# Close the order directly, without automaticly updating ordergroups account balances
|
|
def close_direct!(user)
|
|
raise "Bestellung wurde schon abgerechnet" if closed?
|
|
update_attributes! state: 'closed', updated_by: user
|
|
end
|
|
|
|
protected
|
|
|
|
def starts_before_ends
|
|
errors.add(:ends, "muss nach dem Bestellstart liegen (oder leer bleiben)") if (ends && starts && ends <= starts)
|
|
end
|
|
|
|
def include_articles
|
|
errors.add(:articles, "Es muss mindestens ein Artikel ausgewählt sein") if article_ids.empty?
|
|
end
|
|
|
|
def save_order_articles
|
|
self.articles = Article.find(article_ids)
|
|
end
|
|
|
|
private
|
|
|
|
# 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
|
|
|
|
end
|
|
|