class Work < ActiveRecord::Base extend UserSystem include UserSystem belongs_to :task belongs_to :user belongs_to :work_account belongs_to :customer validates_associated :task validates_presence_of :work_account validates_presence_of :started_on validates_presence_of :start_time, :if => :track_times? def initialize(*args) super self.invoice = true if self.invoice.nil? end def validate errors.add(:work, "Work account is missing") unless work_account if completed_at absence_exists = Absence.exists? ['user_id = ? AND "on" BETWEEN ? AND ?', user_id, started_on, completed_at.to_date] else absence_exists = Absence.exists? :user_id => user_id, :on => started_on end errors.add :started_on, "You have already marked this date with an absence." if absence_exists end alias_method :old_work_account, :work_account def work_account self.old_work_account || (task && task.work_account) end def track_times? work_account && work_account.track_times? end # Return an array with an array of works per day: # [ # [m0, t0, w0, t0, f0, nil, nil], # [m1, t1, w1, t1, f1, nil, nil], # [m2, t2, nil,t2, nil,nil, nil], # [nil,t2, nil,nil,nil,nil, nil], # ] def self.works_for_week(year, week_no, user = current_user) first = Date.commercial(year, week_no, 1) last = first + 6 works = find(:all, :conditions => "completed_at IS NOT NULL AND started_on BETWEEN '#{first}' AND '#{last}'", :order => 'started_on, start_time') length = 0 works_per_day = (0..6).map do |day| works_for_day = works.select {|work| work.started_on == (first + day) && (work.user_id.nil? || (user && work.user_id == user.id)) } length = [length, works_for_day.length].max works_for_day end works_per_day.each {|works_for_day| works_for_day[length-1] ||= nil} if length > 0 works_by_row = works_per_day.transpose end # Return a hash with work accounts as keys an array of work hours totals per day as values: # { # <#WorkAccount#1> => [ 8, 7, 9, 12, 4, nil, nil], # <#WorkAccount#2> => [nil, 1, nil, nil, 4, nil, nil], # <#WorkAccount#3> => [nil, nil, nil, nil, nil, 4, 3], # ] def self.works_for_week_by_work_account(year, week_no, user = current_user) first_date = Date.commercial(year, week_no, 1) last_date = first_date + 6 works = find(:all, :conditions => "user_id #{user ? " = #{user.id}" : "IS NULL"} AND started_on BETWEEN '#{first_date}' AND '#{last_date}'", :order => 'completed_at, started_on, start_time') result = {} works.each do |work| day_of_week = work.started_on.cwday - 1 result[work.work_account] ||= [] result[work.work_account][day_of_week] ||= BigDecimal('0') result[work.work_account][day_of_week] += work.hours end result.values.each {|work_account_totals| work_account_totals[6] ||= nil} result.values.each {|work_account_totals| (0..6).each {|i| work_account_totals[i] = nil if work_account_totals[i] == 0}} result end # Return a hash with an array of work totals per day: # { # backlog1.id => [[m, t, w, t, f, s, s], [m, t, w, t, f, s, s]], # backlog2.id => [, 0, , , , 0, ] # } def self.work_totals_for_week(year, week_no, user = current_user) first = Date.commercial(year, week_no, 1) last = first + 6 works = find(:all, :conditions => "completed_at IS NOT NULL AND started_on BETWEEN '#{first}' AND '#{last}'", :order => 'started_on, start_time, completed_at') totals_per_work_account = {} works.map{|w| w.work_account}.uniq.each do |work_account| totals_per_work_account[work_account.id] = [[], []] (0..6).each do |day| works_for_day = works.select {|work| (work.work_account == work_account) && (work.started_on == (first + day)) && ((user.nil? && work.user_id.nil?) || (user && work.user_id == user.id)) } invoice_works_for_day = works_for_day.select {|work| work.invoice? } internal_works_for_day = works_for_day.select {|work| !work.invoice? } invoice_day_total = invoice_works_for_day.reduce(BigDecimal('0')){|total, work| total += work.hours} internal_day_total = internal_works_for_day.reduce(BigDecimal('0')){|total, work| total += work.hours} totals_per_work_account[work_account.id][0] << invoice_day_total totals_per_work_account[work_account.id][1] << internal_day_total end end totals_per_work_account.reject! do |work_account_id, day_totals| !day_totals[0].find{|day_total| day_total > 0} && !day_totals[1].find{|day_total| day_total > 0} end totals_per_work_account end def self.find_work_for_day(date, user = current_user) Work.find(:all, :conditions => "started_on = '#{date}' AND user_id = #{user.id}", :order => 'start_time, completed_at') end def self.work_sum_per_month(year, month_no, user = current_user) first = Date.new(year, month_no, 1) last = Date.new(year, month_no, -1) count = find_by_sql("select sum(hours) from works where completed_at IS NOT NULL AND started_on BETWEEN '#{first}' AND '#{last}' AND user_id = #{user.id}")[0]['sum'] count ? count : 0 end def self.invoice_work_sum_per_month(year, month_no, user = current_user) first = Date.new(year, month_no, 1) last = Date.new(year, month_no, -1) count = find_by_sql("select sum(hours) from works where invoice = true AND completed_at IS NOT NULL AND started_on BETWEEN '#{first}' AND '#{last}' AND user_id = #{user.id}")[0]['sum'] count ? count : 0 end def self.work_sum_per_week(year, week_no, user = current_user) first = Date.commercial(year, week_no, 1) last = first + 6 count = find_by_sql("select sum(hours) from works where completed_at IS NOT NULL AND started_on BETWEEN '#{first}' AND '#{last}' AND user_id = #{user.id}")[0]['sum'] count ? count : 0 end def self.invoice_work_sum_per_week(year, week_no, user = current_user) first = Date.commercial(year, week_no, 1) last = first + 6 count = find_by_sql("select sum(hours) from works where invoice = true AND completed_at IS NOT NULL AND started_on BETWEEN '#{first}' AND '#{last}' AND user_id = #{user.id}")[0]['sum'] count ? count : 0 end def self.work_sum_per_day(date, user = current_user) count = find_by_sql("select sum(hours) from works where completed_at IS NOT NULL AND started_on = '#{date}' AND user_id = #{user.id}")[0]['sum'] count ? count : 0 end def self.invoice_work_sum_per_day(date, user = current_user) count = find_by_sql("select sum(hours) from works where invoice = true AND completed_at IS NOT NULL AND started_on = '#{date}' AND user_id = #{user.id}")[0]['sum'] count ? count : 0 end def started? completed_at.nil? end def started_at started_on && start_time && started_on.at(start_time) end def started_at=(new_value) case new_value when String: t = Time.parse(new_value) when Time: t = new_value else raise "Illegal argument: #{new_value.inspect}" end self.started_on = Date.new(t.year, t.month, t.day) self.start_time = TimeOfDay.new(t.hour, t.min) end def completed_at_time completed_at && completed_at.time_of_day end def completed_at_time=(new_value) new_value.strip! if new_value.empty? self.completed_at = nil return end time_of_day = TimeOfDay.parse(new_value) date = started_on || completed_at.to_date || Date.today self.completed_at = date.at time_of_day end def calculate_hours! return unless started_at && completed_at self.hours = calculate_hours end def calculate_hours BigDecimal(((completed_at - started_at) / 3600).to_s).round(3) end end