Last active
December 15, 2015 12:09
-
-
Save killthekitten/5258720 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<%= 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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<td> | |
<%= f.hidden_field :workdate %> | |
<%= f.text_field :worktime, { class: "alignCenter", size: 3 } %> | |
</td> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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." |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<% 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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Well done, Niko, thanks