Skip to content

Instantly share code, notes, and snippets.

@killthekitten
Last active December 15, 2015 12:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save killthekitten/5258720 to your computer and use it in GitHub Desktop.
Save killthekitten/5258720 to your computer and use it in GitHub Desktop.
<table>
<tr class='entry_header'>
<th>Task</th>
<% @timesheet.date_range.each do |week_date| %>
<th><%= week_date.to_formatted_s(:short) %></th>
<% end %>
</tr>
<tr class="fields">
<td><%= f.collection_select(:task_id, @tasks, :id, :name, prompt: true) %></td>
<%= f.fields_for :time_entries do |builder|%>
<%= render 'time_entries_fields', f: builder, collection: @entries %>
<% end %>
<% unless f.object.nil? || f.object.new_record? %>
<td><%= link_to_remove_fields "remove", f %></td>
<% end %>
</tr>
</table>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :start_date %>
<%= f.text_field :start_date, { value: @timesheet.start_date.to_date, readonly: "readonly" } %>
<%= f.label :end_date %>
<%= f.text_field :end_date, { value: @timesheet.end_date.to_date, readonly: "readonly" } %>
<h2>Activities:</h2>
<%= f.fields_for :activities do |builder|%>
<%= render "activity_fields", f: builder %>
<% end %>
<p><%= link_to_add_fields "Add Activity", f, :activities, @timesheet %></p>
<td>
<%= f.hidden_field :workdate %>
<%= f.text_field :worktime, { class: "alignCenter", size: 3 } %>
</td>
class Activity < ActiveRecord::Base
attr_accessible :task_id, :timesheet_id, :time_entries_attributes
belongs_to :timesheet, inverse_of: :activities
belongs_to :task
has_many :time_entries, order: :workdate, dependent: :destroy, inverse_of: :activity
accepts_nested_attributes_for :time_entries, allow_destroy: true, reject_if: proc { |a| a[:worktime].blank? }
validates :task_id, presence: true, uniqueness: { scope: 'timesheet_id'}
validates_presence_of :timesheet
validate :one_time_entry_present
def daily_spent(week_day)
daily_spent = ''
index = worked_days.index(week_day)
worked_day = time_entries.at(index) if index
daily_spent = worked_day.worktime if worked_day
daily_spent
end
def worked_days
@worked_days ||= time_entries.map{ |w| w.workdate }
end
def total_work
days = 0
days = time_entries.sum(:worktime) unless time_entries.empty?
days
end
private
def one_time_entry_present
errors[:base] << "At least one day time entry should be present" if time_entries.empty?
end
end
Gist from this rails issue: https://github.com/rails/rails/issues/9934.
"When creating a timesheet with an invalid time entries, i.e. when the entered worked time per days is greater then 1.0, timesheet validation method 'check_an_activity_present' is called but with no errors raised, because at that moment the association timesheet.time_entries is still empty. When I edit the same timesheet and try to change time entries values so that the worked time per day to be <= 1, the above validation method catches the errors despite that the values were updated.
You could see the entire project at the following git repository: https://github.com/Javix/spot-time if more code details are needed. Thank you."
<% provide(:title, 'New Timesheet')%>
<h2>New Timesheet</h2>
<div class='row'>
<%= form_for @timesheet do |f| %>
<%= render 'fields', f: f %>
<%= f.submit class: 'btn btn-large btn-primary'%>
<% end %>
<%= link_to 'Back', timesheets_path %>
</div>
class TimeEntry < ActiveRecord::Base
attr_accessible :activity_id, :workdate, :worktime
belongs_to :activity, :inverse_of => :time_entries
validates :worktime, presence: true, inclusion: { in: [0.5, 1] }
validates_presence_of :activity
end
class Timesheet < ActiveRecord::Base
attr_accessible :status, :user_id, :start_date, :end_date, :activities_attributes
SUBMITTED = 'Submitted'
APPROUVED = 'Approuved'
REJECTED = 'Rejected'
STATUS_VALUES = [SUBMITTED, APPROUVED, REJECTED]
belongs_to :user
has_many :activities, dependent: :destroy, inverse_of: :timesheet
has_many :time_entries, through: :activities
accepts_nested_attributes_for :activities, allow_destroy: true
validates :user_id, presence: true
validates :status, presence: true, inclusion: {in: STATUS_VALUES}
validate :maximum_worktime_per_day
after_update :check_an_activity_present
after_initialize :init_working_week
def date_range
(start_date..end_date).to_a
end
def total_worked_time
days = 0
days = time_entries.sum(:worktime) unless time_entries.empty?
days
end
def create_activity_days(activity)
date_range.each { |date| activity.time_entries.build(workdate: date) }
end
def build_and_sort_time_entries
entries = []
activities.each do |activity|
date_range.each do |week_day|
activity.time_entries.build(workdate: week_day) unless activity.worked_days.include?(week_day)
end
entries = activity.time_entries
entries.sort! { |a, b| a.workdate <=> b.workdate }
end
entries
end
private
def init_working_week
if start_date.blank? || end_date.blank?
set_start_and_end_dates
end
end
def set_start_and_end_dates
filter_date = Date.today
last_timesheet = Timesheet.last
filter_date = last_timesheet.start_date.next_week if last_timesheet
self.start_date = filter_date.beginning_of_week
self.end_date = filter_date.end_of_week
end
def check_an_activity_present
raise "You should have at least ONE activity present" if activities.empty?
end
def maximum_worktime_per_day
time_entries_by_date = time_entries.group_by(&:workdate)
time_entries_by_date.each do |key, value|
errors[:base] << "Maximum daily time should not exceed 1 day" if worktime_by_date(value) > 1
break
end
end
def worktime_by_date(daily_entries)
time = 0
time = daily_entries.map(&:worktime).inject(:+) unless daily_entries.empty?
time
end
end
class TimesheetsController < ApplicationController
before_filter :authenticate_user!
before_filter :load_tasks, except: [:index, :show]
def index
if current_user.admin?
@timesheets = Timesheet.paginate(page: params[:page])
else
@timesheets = current_user.timesheets.paginate(page: params[:page])
end
end
def show
@timesheet = current_user.timesheets.find(params[:id])
@activities = @timesheet.activities.paginate(page: params[:page])
end
def new
@timesheet = current_user.timesheets.new
init_entries
end
def create
@timesheet = current_user.timesheets.new(params[:timesheet])
@timesheet.status = Timesheet::SUBMITTED
if @timesheet.save
flash[:success] = 'Timesheet created sucessfully'
redirect_to @timesheet
else
load_entries
render 'new'
end
end
def edit
@timesheet = current_user.timesheets.find(params[:id])
load_entries
end
def update
begin
@timesheet = current_user.timesheets.find(params[:id])
if @timesheet.update_attributes(params[:timesheet])
flash[:success] = 'Timesheet updated sucessfully'
redirect_to @timesheet
else
load_entries
render 'edit'
end
rescue Exception => e
flash[:error] = e.message
redirect_to edit_timesheet_path(@timesheet)
end
end
def destroy
current_user.timesheets.find(params[:id]).destroy
flash[:success] = "Timesheet destroyed successfully"
redirect_to timesheets_path
end
private
def load_tasks
@tasks = Task.all
end
def init_entries
activity = @timesheet.activities.build
@entries = @timesheet.create_activity_days(activity)
end
def load_entries
@entries = @timesheet.build_and_sort_time_entries
end
end
@belgoros
Copy link

Well done, Niko, thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment