backlog 0.18.0 → 0.19.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +19 -0
- data/app/controllers/periods_controller.rb +0 -14
- data/app/controllers/tasks_controller.rb +15 -4
- data/app/controllers/user_controller.rb +15 -1
- data/app/controllers/work_accounts_controller.rb +1 -1
- data/app/controllers/work_locks_controller.rb +88 -0
- data/app/controllers/works_controller.rb +29 -5
- data/app/helpers/work_locks_helper.rb +2 -0
- data/app/models/task.rb +1 -1
- data/app/models/user.rb +1 -0
- data/app/models/user_notify.rb +15 -0
- data/app/models/work.rb +28 -6
- data/app/models/work_account.rb +2 -0
- data/app/models/work_lock.rb +3 -0
- data/app/models/work_lock_notify.rb +27 -0
- data/app/models/work_lock_subscription.rb +3 -0
- data/app/views/display_notice.rjs +1 -0
- data/app/views/layouts/_left_top.rhtml +1 -0
- data/app/views/tasks/_task.rhtml +2 -2
- data/app/views/user/edit.rhtml +15 -2
- data/app/views/user/toggle_work_lock_monitoring.rjs +2 -0
- data/app/views/user_notify/monitoring_en.rhtml +3 -0
- data/app/views/user_notify/monitoring_no.rhtml +3 -0
- data/app/views/work_lock_notify/lock_en.rhtml +7 -0
- data/app/views/work_lock_notify/lock_no.rhtml +7 -0
- data/app/views/work_locks/_form.rhtml +10 -0
- data/app/views/work_locks/edit.rhtml +9 -0
- data/app/views/work_locks/list.rhtml +27 -0
- data/app/views/work_locks/new.rhtml +8 -0
- data/app/views/work_locks/show.rhtml +8 -0
- data/app/views/works/_buttons.rhtml +5 -0
- data/app/views/works/_form.rhtml +4 -1
- data/app/views/works/_row.rhtml +14 -17
- data/app/views/works/_row_field.rhtml +1 -1
- data/app/views/works/daily_work_sheet.rhtml +16 -25
- data/app/views/works/timeliste.rhtml +12 -12
- data/app/views/works/update_row.rjs +2 -4
- data/app/views/works/weekly_work_sheet.rhtml +17 -17
- data/app/views/works/weekly_work_sheet_by_work_account.rhtml +57 -0
- data/db/migrate/027_create_work_locks.rb +25 -0
- data/db/schema.rb +25 -1
- data/public/images/delete.png +0 -0
- data/public/images/email.png +0 -0
- data/public/images/email_grey.png +0 -0
- data/public/images/refresh.png +0 -0
- data/test/fixtures/work_lock_subscriptions.yml +7 -0
- data/test/fixtures/work_locks.yml +11 -0
- data/test/functional/estimates_controller_test.rb +1 -1
- data/test/functional/groups_controller_test.rb +1 -1
- data/test/functional/user_controller_test.rb +2 -2
- data/test/functional/work_locks_controller_test.rb +93 -0
- data/test/integration/user_system_test.rb +1 -1
- data/test/test_helper.rb +1 -1
- data/test/unit/estimate_test.rb +1 -1
- data/test/unit/group_test.rb +1 -1
- data/test/unit/period_test.rb +1 -1
- data/test/unit/task_test.rb +1 -1
- data/test/unit/user_test.rb +1 -1
- data/test/unit/work_account_test.rb +1 -1
- data/test/unit/work_lock_subscription_test.rb +10 -0
- data/test/unit/work_lock_test.rb +10 -0
- data/test/unit/work_test.rb +1 -1
- metadata +31 -2
data/History.txt
CHANGED
@@ -1,3 +1,21 @@
|
|
1
|
+
== 0.19.0 2008-01-23
|
2
|
+
|
3
|
+
=== Features
|
4
|
+
|
5
|
+
* Added button to recalculate the hours when changing start/stop times in the create/edit work record view.
|
6
|
+
* Added locking of work records, and notification.
|
7
|
+
* Added work accounts list link to navigation top.
|
8
|
+
* Changed weekly work sheet to show totals instead of individual work records.
|
9
|
+
Added links in the weekly work sheet to each day for details.
|
10
|
+
* Improved daily work sheet. Maybe usable now.
|
11
|
+
|
12
|
+
=== Fixes
|
13
|
+
|
14
|
+
* Fixed bug when grabbing/releasing a task and then starting and stopping work.
|
15
|
+
This would render javascript in cleartext.
|
16
|
+
* Changed redirect after login from displaying backlog to welcome view.
|
17
|
+
* Fixed Excel export.
|
18
|
+
|
1
19
|
== 0.18.0 2008-01-21
|
2
20
|
|
3
21
|
=== Features
|
@@ -11,6 +29,7 @@
|
|
11
29
|
=== Fixes
|
12
30
|
|
13
31
|
* Updated graphs in README.txt
|
32
|
+
* Sorted work account list by name.
|
14
33
|
|
15
34
|
== 0.17.6 2008-01-19
|
16
35
|
|
@@ -147,20 +147,6 @@ class PeriodsController < ApplicationController
|
|
147
147
|
move_task_to_period
|
148
148
|
end
|
149
149
|
|
150
|
-
def grab_task
|
151
|
-
@task = Task.find(params[:id])
|
152
|
-
@task.grab
|
153
|
-
load_tasks(@task.period)
|
154
|
-
render :template => '/tasks/_update.rjs'
|
155
|
-
end
|
156
|
-
|
157
|
-
def release_task
|
158
|
-
@task = Task.find(params[:id])
|
159
|
-
@task.release
|
160
|
-
load_tasks(@task.period)
|
161
|
-
render :template => '/tasks/_update.rjs'
|
162
|
-
end
|
163
|
-
|
164
150
|
def finish_task
|
165
151
|
@task = Task.find(params[:id])
|
166
152
|
@task.finish(Task::COMPLETED, true)
|
@@ -196,10 +196,21 @@ class TasksController < ApplicationController
|
|
196
196
|
redirect_to :controller => 'periods', :action => :show_no_layout, :id => task.period_id, :task => task.id
|
197
197
|
end
|
198
198
|
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
199
|
+
def grab
|
200
|
+
@task = Task.find(params[:id])
|
201
|
+
@task.grab
|
202
|
+
detour = pop_detour
|
203
|
+
params.update(detour) if detour
|
204
|
+
render :template => '/tasks/_update.rjs'
|
205
|
+
end
|
206
|
+
|
207
|
+
def release
|
208
|
+
@task = Task.find(params[:id])
|
209
|
+
@task.release
|
210
|
+
detour = pop_detour
|
211
|
+
params.update(detour) if detour
|
212
|
+
render :template => '/tasks/_update.rjs'
|
213
|
+
end
|
203
214
|
|
204
215
|
def start_work
|
205
216
|
@task = Task.find(params[:id])
|
@@ -14,7 +14,7 @@ class UserController < ApplicationController
|
|
14
14
|
cookies[:autologin] = {:value => user.id.to_s, :expires =>90.days.from_now}
|
15
15
|
cookies[:token] = {:value => user.security_token, :expires =>90.days.from_now}
|
16
16
|
end
|
17
|
-
back_or_redirect_to :controller => '
|
17
|
+
back_or_redirect_to :controller => 'welcome', :action => :index
|
18
18
|
else
|
19
19
|
@login = params['user']['login']
|
20
20
|
flash[:notice] = 'Login failed'
|
@@ -155,6 +155,20 @@ class UserController < ApplicationController
|
|
155
155
|
@users = User.find(:all)
|
156
156
|
end
|
157
157
|
|
158
|
+
def toggle_work_lock_monitoring
|
159
|
+
@user = User.find(params[:id])
|
160
|
+
already_monitoring = @user.work_lock_subscribers.include? current_user
|
161
|
+
if already_monitoring
|
162
|
+
@user.work_lock_subscribers.delete current_user
|
163
|
+
action = 'stopped'
|
164
|
+
else
|
165
|
+
@user.work_lock_subscribers << current_user
|
166
|
+
action = 'started'
|
167
|
+
end
|
168
|
+
flash[:notice] = "Monitoring #{action}"
|
169
|
+
UserNotify.deliver_monitoring(@user, current_user, action)
|
170
|
+
end
|
171
|
+
|
158
172
|
protected
|
159
173
|
|
160
174
|
def protect?(action)
|
@@ -9,7 +9,7 @@ class WorkAccountsController < ApplicationController
|
|
9
9
|
:redirect_to => { :action => :list }
|
10
10
|
|
11
11
|
def list
|
12
|
-
@work_accounts = WorkAccount.paginate :page => params[:page]
|
12
|
+
@work_accounts = WorkAccount.paginate :page => params[:page], :order => 'name'
|
13
13
|
end
|
14
14
|
|
15
15
|
def show
|
@@ -0,0 +1,88 @@
|
|
1
|
+
class WorkLocksController < ApplicationController
|
2
|
+
def index
|
3
|
+
list
|
4
|
+
render :action => 'list'
|
5
|
+
end
|
6
|
+
|
7
|
+
# GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
|
8
|
+
verify :method => :post, :only => [ :destroy, :create, :update ],
|
9
|
+
:redirect_to => { :action => :list }
|
10
|
+
|
11
|
+
def list
|
12
|
+
@work_lock_pages, @work_locks = paginate :work_locks, :per_page => 10
|
13
|
+
end
|
14
|
+
|
15
|
+
def show
|
16
|
+
@work_lock = WorkLock.find(params[:id])
|
17
|
+
end
|
18
|
+
|
19
|
+
def new
|
20
|
+
@work_lock = WorkLock.new
|
21
|
+
end
|
22
|
+
|
23
|
+
def create
|
24
|
+
@work_lock = WorkLock.new(params[:work_lock])
|
25
|
+
if @work_lock.save
|
26
|
+
flash[:notice] = 'WorkLock was successfully created.'
|
27
|
+
redirect_to :action => 'list'
|
28
|
+
else
|
29
|
+
render :action => 'new'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def edit
|
34
|
+
@work_lock = WorkLock.find(params[:id])
|
35
|
+
end
|
36
|
+
|
37
|
+
def update
|
38
|
+
@work_lock = WorkLock.find(params[:id])
|
39
|
+
if @work_lock.update_attributes(params[:work_lock])
|
40
|
+
flash[:notice] = 'WorkLock was successfully updated.'
|
41
|
+
redirect_to :action => 'show', :id => @work_lock
|
42
|
+
else
|
43
|
+
render :action => 'edit'
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def destroy
|
48
|
+
WorkLock.find(params[:id]).destroy
|
49
|
+
redirect_to :action => 'list'
|
50
|
+
end
|
51
|
+
|
52
|
+
def lock
|
53
|
+
@year = (params[:year] && params[:year].to_i) || Date.today.year
|
54
|
+
@week = (params[:week] && params[:week].to_i) || Date.today.cweek
|
55
|
+
first_date = Date.commercial(@year, @week, 1)
|
56
|
+
last_date = first_date + 6
|
57
|
+
@lock = WorkLock.find(:first, :conditions => ['user_id = ? AND start_on <= ? AND end_on >= ?', current_user.id, first_date, last_date])
|
58
|
+
raise "Already locked" if @lock
|
59
|
+
WorkLock.create! :user_id => current_user.id, :start_on => first_date, :end_on => last_date
|
60
|
+
work_account_subscribers = Work.works_for_week(@year, @week).flatten.compact.map {|w| w.work_account}.uniq.map {|wa| wa.work_lock_subscribers}.flatten.uniq
|
61
|
+
user_subscribers = current_user.work_lock_subscribers
|
62
|
+
notify_users = (work_account_subscribers + user_subscribers).uniq
|
63
|
+
notify_users.each do |user|
|
64
|
+
week_url = url_for :controller => 'works', :action => :weekly_work_sheet_by_work_account, :year => @year, :week => @week
|
65
|
+
spreadsheet_url = url_for :controller => 'works', :action => :timeliste, :year => @year, :week => @week
|
66
|
+
WorkLockNotify.deliver_lock(user, current_user, @week, week_url, spreadsheet_url)
|
67
|
+
end
|
68
|
+
back_or_redirect_to :controller => 'works', :action => :weekly_work_sheet, :year => @year, :week => @week
|
69
|
+
end
|
70
|
+
|
71
|
+
def unlock
|
72
|
+
get_lock_from_params
|
73
|
+
raise "Not locked" unless @lock
|
74
|
+
raise "Unable to destroy" unless @lock.destroy
|
75
|
+
back_or_redirect_to :controller => 'works', :action => :weekly_work_sheet, :year => @year, :week => @week
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def get_lock_from_params
|
81
|
+
@year = (params[:year] && params[:year].to_i) || Date.today.year
|
82
|
+
@week = (params[:week] && params[:week].to_i) || Date.today.cweek
|
83
|
+
@first_date = Date.commercial(@year, @week, 1)
|
84
|
+
@last_date = @first_date + 6
|
85
|
+
@lock = WorkLock.find(:first, :conditions => ['user_id = ? AND start_on <= ? AND end_on >= ?', current_user.id, @first_date, @last_date])
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
@@ -3,7 +3,7 @@ class WorksController < ApplicationController
|
|
3
3
|
in_place_edit_for :work, :invoice
|
4
4
|
in_place_edit_for :work, :started_at_time
|
5
5
|
in_place_edit_for :work, :completed_at_time
|
6
|
-
skip_before_filter :populate_layout, :except => [:create, :destroy, :edit, :index, :list, :new, :show, :update]
|
6
|
+
skip_before_filter :populate_layout, :except => [:create, :destroy, :edit, :index, :list, :new, :show, :update, :daily_work_sheet, :weekly_work_sheet_by_work_account]
|
7
7
|
auto_complete_for :work, :description
|
8
8
|
|
9
9
|
def index
|
@@ -73,7 +73,7 @@ class WorksController < ApplicationController
|
|
73
73
|
@work.attributes = params[:work]
|
74
74
|
@estimate = Estimate.new(params[:estimate])
|
75
75
|
@work_accounts = WorkAccount.find(:all)
|
76
|
-
@tasks = Task.find_open
|
76
|
+
@tasks = [@work.task] + Task.find_open
|
77
77
|
@users = User.find(:all)
|
78
78
|
end
|
79
79
|
|
@@ -91,6 +91,7 @@ class WorksController < ApplicationController
|
|
91
91
|
update_work
|
92
92
|
flash.discard
|
93
93
|
@field = params[:field] || 'started_at_time'
|
94
|
+
@work_accounts = WorkAccount.find(:all, :order => :name)
|
94
95
|
end
|
95
96
|
|
96
97
|
def update_time
|
@@ -133,6 +134,7 @@ class WorksController < ApplicationController
|
|
133
134
|
@works = Work.find_work_for_day @date
|
134
135
|
@started_works = Task.find_started
|
135
136
|
@work = Work.new(:started_at => Time.now, :completed_at => Time.now)
|
137
|
+
@work_accounts = WorkAccount.find(:all, :order => :name)
|
136
138
|
render :layout => 'wide'
|
137
139
|
end
|
138
140
|
|
@@ -140,12 +142,26 @@ class WorksController < ApplicationController
|
|
140
142
|
@year = (params[:year] && params[:year].to_i) || Date.today.year
|
141
143
|
@week = (params[:week] && params[:week].to_i) || Date.today.cweek
|
142
144
|
@rows = Work.works_for_week(@year, @week)
|
145
|
+
@first_date = Date.commercial(@year, @week, 1)
|
146
|
+
@last_date = @first_date + 6
|
147
|
+
@lock = WorkLock.find(:first, :conditions => ['user_id = ? AND start_on <= ? AND end_on >= ?', current_user.id, @first_date, @last_date])
|
148
|
+
render :layout => 'wide'
|
149
|
+
end
|
150
|
+
|
151
|
+
def weekly_work_sheet_by_work_account
|
152
|
+
@year = (params[:year] && params[:year].to_i) || Date.today.year
|
153
|
+
@week = (params[:week] && params[:week].to_i) || Date.today.cweek
|
154
|
+
@work_accounts = Work.works_for_week_by_work_account(@year, @week)
|
155
|
+
@first_date = Date.commercial(@year, @week, 1)
|
156
|
+
@last_date = @first_date + 6
|
157
|
+
@lock = WorkLock.find(:first, :conditions => ['user_id = ? AND start_on <= ? AND end_on >= ?', current_user.id, @first_date, @last_date])
|
143
158
|
render :layout => 'wide'
|
144
159
|
end
|
145
160
|
|
146
161
|
def timeliste
|
147
|
-
@
|
148
|
-
@
|
162
|
+
@year = (params[:year] && params[:year].to_i) || Date.today.year
|
163
|
+
@week = (params[:week] && params[:week].to_i) || Date.today.cweek
|
164
|
+
@work_totals_per_work_account = Work.work_totals_for_week(@year, @week)
|
149
165
|
headers["Content-Type"] = "application/vnd.ms-excel"
|
150
166
|
headers["Content-Disposition"] = 'attachment; filename="export.xml"'
|
151
167
|
render :layout => false
|
@@ -183,6 +199,12 @@ class WorksController < ApplicationController
|
|
183
199
|
|
184
200
|
render :partial => 'description_list'
|
185
201
|
end
|
202
|
+
|
203
|
+
def calculate_hours
|
204
|
+
calculated_duration = (Time.parse(params[:completed_at]) - Time.parse(params[:started_at]))
|
205
|
+
new_value = '%2d:%02d' % [calculated_duration / 3600, (calculated_duration % 3600) / 60]
|
206
|
+
render :update do |page| page['work_hours_time'].value = new_value end
|
207
|
+
end
|
186
208
|
|
187
209
|
private
|
188
210
|
|
@@ -194,7 +216,9 @@ class WorksController < ApplicationController
|
|
194
216
|
else
|
195
217
|
raise "Unknown time format: '#{params[:work][:hours_time]}'"
|
196
218
|
end
|
197
|
-
if
|
219
|
+
if params[:work][:completed_at]
|
220
|
+
t = Time.parse params[:work][:completed_at]
|
221
|
+
elsif @work && @work.started?
|
198
222
|
t = @work.started_at
|
199
223
|
else
|
200
224
|
t = Time.now
|
data/app/models/task.rb
CHANGED
@@ -54,7 +54,7 @@ class Task < ActiveRecord::Base
|
|
54
54
|
user_clause = " OR user_id = #{current_user.id}"
|
55
55
|
end
|
56
56
|
conditions = "completed_at IS NULL AND (user_id IS NULL#{user_clause})"
|
57
|
-
Work.find(:all, :conditions => conditions).map {|work| work.task}.sort_by do |t|
|
57
|
+
Work.find(:all, :conditions => conditions).map {|work| work.task}.compact.sort_by do |t|
|
58
58
|
[t.root_task.backlog.name, t.root_task.period.end_on, t.position || 0]
|
59
59
|
end
|
60
60
|
end
|
data/app/models/user.rb
CHANGED
@@ -8,6 +8,7 @@ class User < Party
|
|
8
8
|
# TODO (uwe): We need to specify :join_table, :foreign_key and :association_foreign_key
|
9
9
|
# even if they follow the defaults since ClassTableInheritanceInRails breaks it.
|
10
10
|
has_and_belongs_to_many :groups, :join_table => "groups_users", :foreign_key => "user_id", :association_foreign_key => 'group_id'
|
11
|
+
has_and_belongs_to_many :work_lock_subscribers, :class_name => 'User', :join_table => "user_work_lock_subscriptions", :foreign_key => "user_id", :association_foreign_key => 'subscriber_user_id'
|
11
12
|
|
12
13
|
attr_accessor :password_needs_confirmation
|
13
14
|
|
data/app/models/user_notify.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
class UserNotify < ActionMailer::Base
|
2
|
+
include Localization
|
3
|
+
|
2
4
|
def signup(user, password, url=nil)
|
3
5
|
setup_email(user)
|
4
6
|
|
@@ -40,6 +42,19 @@ class UserNotify < ActionMailer::Base
|
|
40
42
|
@body["app_name"] = UserSystem::CONFIG[:app_name].to_s
|
41
43
|
end
|
42
44
|
|
45
|
+
def monitoring(user, monitoring_user, action)
|
46
|
+
setup_email(user)
|
47
|
+
|
48
|
+
@subject += "#{monitoring_user.name} has #{l(action)} monitoring your time sheets."
|
49
|
+
|
50
|
+
@body["app_name"] = UserSystem::CONFIG[:app_name].to_s
|
51
|
+
@body["user"] = user
|
52
|
+
@body["monitoring_user"] = monitoring_user
|
53
|
+
@body["action"] = action
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
43
58
|
def setup_email(user)
|
44
59
|
@recipients = "#{user.email}"
|
45
60
|
@from = UserSystem::CONFIG[:email_from].to_s
|
data/app/models/work.rb
CHANGED
@@ -44,6 +44,28 @@ class Work < ActiveRecord::Base
|
|
44
44
|
works_by_row = works_per_day.transpose
|
45
45
|
end
|
46
46
|
|
47
|
+
# Return a hash with work accounts as keys an array of work hours totals per day as values:
|
48
|
+
# {
|
49
|
+
# <#WorkAccount#1> => [ 8, 7, 9, 12, 4, nil, nil],
|
50
|
+
# <#WorkAccount#2> => [nil, 1, nil, nil, 4, nil, nil],
|
51
|
+
# <#WorkAccount#3> => [nil, nil, nil, nil, nil, 4, 3],
|
52
|
+
# ]
|
53
|
+
def self.works_for_week_by_work_account(year, week_no, user = current_user)
|
54
|
+
first_date = Date.commercial(year, week_no, 1)
|
55
|
+
last_date = first_date + 6
|
56
|
+
works = find(:all, :conditions => "completed_at IS NOT NULL AND completed_at BETWEEN '#{first_date.to_time.iso8601}' AND '#{(last_date+1).to_time.iso8601}'", :order => 'completed_at, started_at')
|
57
|
+
result = {}
|
58
|
+
works.each do |work|
|
59
|
+
day_of_week = work.completed_at.to_date.cwday - 1
|
60
|
+
result[work.work_account] ||= []
|
61
|
+
result[work.work_account][day_of_week] ||= BigDecimal('0')
|
62
|
+
result[work.work_account][day_of_week] += work.hours
|
63
|
+
end
|
64
|
+
result.values.each {|work_account_totals| work_account_totals[6] ||= nil}
|
65
|
+
result.values.each {|work_account_totals| (0..6).each {|i| work_account_totals[i] = nil if work_account_totals[i] == 0}}
|
66
|
+
result
|
67
|
+
end
|
68
|
+
|
47
69
|
# Return a hash with an array of work totals per day:
|
48
70
|
# {
|
49
71
|
# backlog1.id => [[m, t, w, t, f, s, s], [m, t, w, t, f, s, s]],
|
@@ -52,12 +74,12 @@ class Work < ActiveRecord::Base
|
|
52
74
|
def self.work_totals_for_week(year, week_no, user = current_user)
|
53
75
|
first = Date.commercial(year, week_no, 1)
|
54
76
|
last = first + 7
|
55
|
-
works = find(:all, :conditions => "completed_at IS NOT NULL AND
|
77
|
+
works = find(:all, :conditions => "completed_at IS NOT NULL AND completed_at BETWEEN '#{first.to_time.iso8601}' AND '#{last.to_time.iso8601}'", :order => 'completed_at, started_at')
|
56
78
|
totals_per_work_account = {}
|
57
|
-
|
79
|
+
works.map{|w| w.work_account}.uniq.each do |work_account|
|
58
80
|
totals_per_work_account[work_account.id] = [[], []]
|
59
81
|
(0..6).each do |day|
|
60
|
-
works_for_day = works.select {|work| (work.work_account == work_account) && (work.
|
82
|
+
works_for_day = works.select {|work| (work.work_account == work_account) && (work.completed_at.to_date == (first + day)) && (work.user_id.nil? || (user && work.user_id == user.id)) }
|
61
83
|
invoice_works_for_day = works_for_day.select {|work| work.invoice? }
|
62
84
|
internal_works_for_day = works_for_day.select {|work| !work.invoice? }
|
63
85
|
|
@@ -74,8 +96,8 @@ class Work < ActiveRecord::Base
|
|
74
96
|
end
|
75
97
|
|
76
98
|
def self.find_work_for_day date
|
77
|
-
Work.find(:all, :conditions => "
|
78
|
-
:order => 'completed_at')
|
99
|
+
Work.find(:all, :conditions => "(completed_at IS NULL OR completed_at BETWEEN '#{date}' AND '#{date+1}') AND user_id = #{current_user.id}",
|
100
|
+
:order => 'completed_at, started_at')
|
79
101
|
end
|
80
102
|
|
81
103
|
def started?
|
@@ -94,7 +116,7 @@ class Work < ActiveRecord::Base
|
|
94
116
|
end
|
95
117
|
|
96
118
|
def completed_at_time
|
97
|
-
completed_at.strftime('%H:%M')
|
119
|
+
completed_at && completed_at.strftime('%H:%M')
|
98
120
|
end
|
99
121
|
|
100
122
|
def completed_at_time=(new_value)
|
data/app/models/work_account.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
class WorkAccount < ActiveRecord::Base
|
2
2
|
has_many :backlogs, :dependent => :nullify
|
3
3
|
has_many :works, :dependent => :destroy
|
4
|
+
has_and_belongs_to_many :work_lock_subscribers, :class_name => 'User', :join_table => "work_lock_subscriptions", :foreign_key => "work_account_id", :association_foreign_key => 'subscriber_user_id'
|
4
5
|
|
5
6
|
validates_inclusion_of :track_times, :in => [true, false], :allow_nil => true, :message => ActiveRecord::Errors.default_error_messages[:blank]
|
6
7
|
validates_length_of :invoice_code, :allow_nil => true, :maximum => 255
|
8
|
+
validates_uniqueness_of :name
|
7
9
|
|
8
10
|
def enable_invoicing?
|
9
11
|
invoice_code && invoice_code.length > 0
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class WorkLockNotify < ActionMailer::Base
|
2
|
+
def lock(user, lock_user, week, week_url, spreadsheet_url)
|
3
|
+
setup_email(user)
|
4
|
+
|
5
|
+
# Email header info
|
6
|
+
@subject += "#{lock_user.name} has marked week #{week} as locked."
|
7
|
+
|
8
|
+
# Email body substitutions
|
9
|
+
@body["app_name"] = UserSystem::CONFIG[:app_name].to_s
|
10
|
+
@body["app_url"] = UserSystem::CONFIG[:app_url].to_s
|
11
|
+
@body["name"] = "#{user.first_name} #{user.last_name}"
|
12
|
+
@body["login"] = user.login
|
13
|
+
@body["lock_user"] = lock_user
|
14
|
+
@body["week_url"] = week_url
|
15
|
+
@body["spreadsheet_url"] = spreadsheet_url
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def setup_email(user)
|
21
|
+
@recipients = "#{user.email}"
|
22
|
+
@from = UserSystem::CONFIG[:email_from].to_s
|
23
|
+
@subject = "[#{UserSystem::CONFIG[:app_name]}] "
|
24
|
+
@sent_on = Time.now
|
25
|
+
@headers['Content-Type'] = "text/plain; charset=#{UserSystem::CONFIG[:mail_charset]}; format=flowed"
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
display_notice(page)
|
@@ -6,6 +6,7 @@
|
|
6
6
|
|
7
7
|
<% if user? %>
|
8
8
|
| <%= link_to l(:backlogs), :controller => 'backlogs', :action => :list %>
|
9
|
+
| <%= link_to l(:work_accounts), :controller => 'work_accounts', :action => :list %>
|
9
10
|
| <%= link_to l(:log_out), :controller => 'user', :action => :logout %>
|
10
11
|
<% else %>
|
11
12
|
| <%= detour_to l(:log_in), :controller => 'user', :action => :login %>
|
data/app/views/tasks/_task.rhtml
CHANGED
@@ -87,9 +87,9 @@
|
|
87
87
|
<% unless @task.work_started? -%>
|
88
88
|
<% if @task.backlog.enable_users? %>
|
89
89
|
<% if @task.users.include?(current_user) %>
|
90
|
-
<%=image_link_to_remote('grab.png', l(:grab_task), {:action => :
|
90
|
+
<%=image_link_to_remote('grab.png', l(:grab_task), with_detour({:controller => 'tasks', :action => :release, :id => @task}), nil, true)%>
|
91
91
|
<% else %>
|
92
|
-
<%=image_link_to_remote('grab_gray.png', l(:grab_task), {:action => :
|
92
|
+
<%=image_link_to_remote('grab_gray.png', l(:grab_task), with_detour({:controller => 'tasks', :action => :grab, :id => @task}), nil, true)%>
|
93
93
|
<% end %>
|
94
94
|
<% end %>
|
95
95
|
<%=image_link_to_remote('arrow_right.png', l(:move_to_next_period), {:action => :move_task_to_next_period, :id => @task}, nil, true) if @task.backlog.enable_periods? || @task.period_id%>
|
data/app/views/user/edit.rhtml
CHANGED
@@ -1,6 +1,19 @@
|
|
1
|
-
|
2
|
-
<% @page_title = "#{l :editing} #{l :user}" %>
|
1
|
+
<% @page_title = "#{l :editing} #{l :user}" %>
|
3
2
|
|
3
|
+
<div id="spotlight" title="<%= title_helper %>" class="form">
|
4
|
+
<% if @user == current_user %>
|
5
|
+
<% unless @user.work_lock_subscribers.empty? %>
|
6
|
+
<table align="right">
|
7
|
+
<tr><th>Subscribers</th></tr>
|
8
|
+
<% for subscriber in @user.work_lock_subscribers %>
|
9
|
+
<tr><td><%=detour_to subscriber.name, :action => :edit%></td></tr>
|
10
|
+
<% end %>
|
11
|
+
</table>
|
12
|
+
<% end %>
|
13
|
+
<% else %>
|
14
|
+
<% monitoring = @user.work_lock_subscribers.include? current_user %>
|
15
|
+
<%=image_link_to_remote "email#{'_grey' unless monitoring}.png", "#{l(monitoring ? :stop : :start)} #{l(:monitoring)}", {:action => :toggle_work_lock_monitoring, :id => @user.id}, {:id => :work_lock_monitor_icon, :style => 'float: right'} %>
|
16
|
+
<% end %>
|
4
17
|
<%= start_form_tag_helper %>
|
5
18
|
<%= render_partial 'edit', :user => @user, :submit => true %>
|
6
19
|
</form>
|
@@ -0,0 +1,10 @@
|
|
1
|
+
<%= error_messages_for 'work_lock' %>
|
2
|
+
|
3
|
+
<!--[form:work_lock]-->
|
4
|
+
<p><label for="work_lock_start_on">Start on</label><br/>
|
5
|
+
<%= date_select 'work_lock', 'start_on' %></p>
|
6
|
+
|
7
|
+
<p><label for="work_lock_end_on">End on</label><br/>
|
8
|
+
<%= date_select 'work_lock', 'end_on' %></p>
|
9
|
+
<!--[eoform:work_lock]-->
|
10
|
+
|
@@ -0,0 +1,9 @@
|
|
1
|
+
<h1>Editing work_lock</h1>
|
2
|
+
|
3
|
+
<% form_tag :action => 'update', :id => @work_lock do %>
|
4
|
+
<%= render :partial => 'form' %>
|
5
|
+
<%= submit_tag 'Edit' %>
|
6
|
+
<% end %>
|
7
|
+
|
8
|
+
<%= link_to 'Show', :action => 'show', :id => @work_lock %> |
|
9
|
+
<%= link_to 'Back', :action => 'list' %>
|
@@ -0,0 +1,27 @@
|
|
1
|
+
<h1>Listing work_locks</h1>
|
2
|
+
|
3
|
+
<table>
|
4
|
+
<tr>
|
5
|
+
<% for column in WorkLock.content_columns %>
|
6
|
+
<th><%= column.human_name %></th>
|
7
|
+
<% end %>
|
8
|
+
</tr>
|
9
|
+
|
10
|
+
<% for work_lock in @work_locks %>
|
11
|
+
<tr>
|
12
|
+
<% for column in WorkLock.content_columns %>
|
13
|
+
<td><%=h work_lock.send(column.name) %></td>
|
14
|
+
<% end %>
|
15
|
+
<td><%= link_to 'Show', :action => 'show', :id => work_lock %></td>
|
16
|
+
<td><%= link_to 'Edit', :action => 'edit', :id => work_lock %></td>
|
17
|
+
<td><%= link_to 'Destroy', { :action => 'destroy', :id => work_lock }, :confirm => 'Are you sure?', :method => :post %></td>
|
18
|
+
</tr>
|
19
|
+
<% end %>
|
20
|
+
</table>
|
21
|
+
|
22
|
+
<%= link_to 'Previous page', { :page => @work_lock_pages.current.previous } if @work_lock_pages.current.previous %>
|
23
|
+
<%= link_to 'Next page', { :page => @work_lock_pages.current.next } if @work_lock_pages.current.next %>
|
24
|
+
|
25
|
+
<br />
|
26
|
+
|
27
|
+
<%= link_to 'New work_lock', :action => 'new' %>
|
@@ -1,3 +1,8 @@
|
|
1
|
+
<%=image_detour_to('hammer.png', l(:weekly_work_sheet), :controller => 'works', :action => :weekly_work_sheet_by_work_account)%>
|
2
|
+
<!--
|
1
3
|
<%=image_detour_to('hammer.png', l(:weekly_work_sheet), :controller => 'works', :action => :weekly_work_sheet)%>
|
4
|
+
-->
|
2
5
|
<%=link_to(image_tag(url_for("hammer.png"), :alt => l(:daily_work_sheet), :title => l(:daily_work_sheet), :class => 'image-submit'), :controller => 'works', :action => :daily_work_sheet)%>
|
6
|
+
<!--
|
3
7
|
<%=link_to(image_tag(url_for("hammer.png"), :alt => l(:edit_works), :title => l(:edit_works), :class => 'image-submit'), :controller => 'periods', :action => 'list_work', :id => (@period ? @period.id : (@backlog && @backlog.periods.first ? @backlog.periods.first.id : nil))) if @period || @backlog%>
|
8
|
+
-->
|
data/app/views/works/_form.rhtml
CHANGED
@@ -63,7 +63,10 @@
|
|
63
63
|
|
64
64
|
<% if @work.task.nil? || @work.task.track_done?%>
|
65
65
|
<p style="float: left;"><label for="work_hours"><%=l :hours%></label><br/>
|
66
|
-
<%=
|
66
|
+
<%=text_field 'work', 'hours_time', :class => :task_hours, :value => t(@work.hours) %></p>
|
67
|
+
|
68
|
+
<p style="float: left;"><br/><%=image_link_to_remote 'refresh.png', l(:refresh), :action => :calculate_hours, :started_at => "' + $('work_started_at').value + '", :completed_at => "' + $('work_completed_at').value + '" %></p>
|
69
|
+
|
67
70
|
<% end %>
|
68
71
|
|
69
72
|
<% if @work.task.nil? || @work.task.enable_invoicing? %>
|