xteam_schedule 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +324 -0
- data/lib/xteam_schedule/core.rb +31 -0
- data/lib/xteam_schedule/facilitation/composer.rb +166 -0
- data/lib/xteam_schedule/facilitation/db.rb +83 -0
- data/lib/xteam_schedule/facilitation/io.rb +11 -0
- data/lib/xteam_schedule/facilitation/parser.rb +168 -0
- data/lib/xteam_schedule/models/assignment.rb +51 -0
- data/lib/xteam_schedule/models/assignment_group.rb +7 -0
- data/lib/xteam_schedule/models/interface.rb +11 -0
- data/lib/xteam_schedule/models/resource.rb +22 -0
- data/lib/xteam_schedule/models/resource_group.rb +7 -0
- data/lib/xteam_schedule/models/schedule.rb +25 -0
- data/lib/xteam_schedule/models/weekly_working_schedule.rb +24 -0
- data/lib/xteam_schedule/models/working_day.rb +28 -0
- data/lib/xteam_schedule/models/working_time.rb +9 -0
- data/lib/xteam_schedule.rb +22 -0
- metadata +163 -0
data/README.md
ADDED
@@ -0,0 +1,324 @@
|
|
1
|
+
## Introduction
|
2
|
+
|
3
|
+
xTeam Schedule is a gem that provides full control over schedules for use with [adnX's xTeam](http://www.adnx.com/i/apps/xteam4mac) software.
|
4
|
+
|
5
|
+
It is capable of reading and writing schedules, whilst providing access to all of its components through the [ActiveRecord](http://api.rubyonrails.org/classes/ActiveRecord/Base.html) interface, which every Ruby on Rails developer will be familiar with. This is absolutely, **the best solution** for managing agile teams.
|
6
|
+
|
7
|
+
<img src="http://www.adnx.com/i/uploads/xTeam1.jpg" width="820" alt="xTeam Schedule" />
|
8
|
+
|
9
|
+
### Features:
|
10
|
+
|
11
|
+
* **Read and write schedules** and interact with in-memory models through the ActiveRecord interface
|
12
|
+
* **Customise everything**; resources, assignments, groups, colours, interface settings..
|
13
|
+
* **Intuitive naming** of models, that correspond to what you see on screen
|
14
|
+
* **Full test coverage**, giving confidence to highly dynamic businesses everywhere
|
15
|
+
|
16
|
+
### Disclaimer
|
17
|
+
|
18
|
+
I am in no way associated with adnX. I work for an agile development company that makes use of xTeam. This project is open-source.
|
19
|
+
|
20
|
+
## Getting Started
|
21
|
+
|
22
|
+
It is not required that you have [xTeam](http://www.adnx.com/i/apps/xteam4mac) installed. However, you will not be able to visualise your schedules otherwise.
|
23
|
+
|
24
|
+
**Install the gem:**
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
gem install xteam_schedule
|
28
|
+
```
|
29
|
+
|
30
|
+
**Require it in your project:**
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
require 'xteam_schedule'
|
34
|
+
```
|
35
|
+
|
36
|
+
You may need to require 'rubygems' too, if you are running an old version of Ruby.
|
37
|
+
|
38
|
+
**Create a Schedule**
|
39
|
+
|
40
|
+
You can create a new schedule, or read one from a file:
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
schedule = XTeamSchedule.new
|
44
|
+
schedule = XTeamSchedule.new('path/to/file.xtps')
|
45
|
+
```
|
46
|
+
|
47
|
+
## Schedules
|
48
|
+
|
49
|
+
Schedules are the top level model through which you access everything. The inspect method is custom-made to give you an overview of the contents of the schedule:
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
XTeamSchedule.new('path/to/file.xtps')
|
53
|
+
=> #<XTeamSchedule resoruce_groups(9), resources(42), assignment_groups(14), assignments(118), working_times(79)>
|
54
|
+
```
|
55
|
+
|
56
|
+
A schedule has many resource groups and assignment groups. It also has many resources and assignments through resource groups and assignment groups respectively. Finally, a schedule has many working times through either resource groups then resources or assignment groups then assignments.
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
schedule = XTeamSchedule.new('path/to/file.xtps')
|
60
|
+
|
61
|
+
resource_groups = schedule.resource_groups
|
62
|
+
resources = schedule.resources # or schedule.resource_groups.map(&:resources).flatten
|
63
|
+
assignment_groups = schedule.assignment_groups
|
64
|
+
assignments = schedule.assignments
|
65
|
+
working_times = schedule.working_times
|
66
|
+
```
|
67
|
+
|
68
|
+
There are numerous other models, for example a schedule has one 'interface' which contains various display settings. These are explained in detail below. It is also possible to convert a schedule to/from a hash. After serialisation, this could easily be written to a database.
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
hash = schedule.hash
|
72
|
+
schedule = XTeamSchedule.new(hash)
|
73
|
+
```
|
74
|
+
|
75
|
+
## Resource Groups
|
76
|
+
|
77
|
+
Resource groups contain resources. Typical names for resource groups might be 'Management', 'Sales', or 'Developers'.
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
schedule.resource_groups.create!(
|
81
|
+
:name => 'Management',
|
82
|
+
:expanded_in_library => true
|
83
|
+
)
|
84
|
+
```
|
85
|
+
|
86
|
+
**Required attributes:**
|
87
|
+
name
|
88
|
+
|
89
|
+
**Defaults:**
|
90
|
+
expanded_in_library => true
|
91
|
+
|
92
|
+
**Examples queries:**
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
resource_groups = schedule.resource_groups
|
96
|
+
|
97
|
+
number_of_groups = resource_groups.count
|
98
|
+
junior_developers = resource_groups.find_by_name('Junior Developers').resources
|
99
|
+
developer_groups = resource_groups.where('name like "%developer%"')
|
100
|
+
```
|
101
|
+
|
102
|
+
## Resources
|
103
|
+
|
104
|
+
A resource is an employee. A resource can not be in multiple resource groups.
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
developers = schedule.resource_groups.create!(:name => 'Developers')
|
108
|
+
developers.resources.create!(
|
109
|
+
:name => 'Christopher Patuzzo',
|
110
|
+
:email => 'chris@example.com',
|
111
|
+
:mobile => '0123456789',
|
112
|
+
:phone => '9876543210',
|
113
|
+
:displayed_in_planning => true
|
114
|
+
)
|
115
|
+
```
|
116
|
+
|
117
|
+
**Required attributes:**
|
118
|
+
name
|
119
|
+
|
120
|
+
**Defaults:**
|
121
|
+
displayed_in_planning => true
|
122
|
+
|
123
|
+
**Example queries:**
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
resources = schedule.resources
|
127
|
+
|
128
|
+
chris_mobile = resources.find_by_name('Christopher Patuzzo').mobile
|
129
|
+
gmail_resource_names = resources.where('email like "%gmail%"').map(&:name)
|
130
|
+
resources.each { |r| r.update_attribute(:displayed_in_planning, true) }
|
131
|
+
```
|
132
|
+
|
133
|
+
## Assignment Groups
|
134
|
+
|
135
|
+
Assignment groups are almost identical to resource groups. Typical names might be 'Training' and 'Research'.
|
136
|
+
|
137
|
+
```ruby
|
138
|
+
schedule.assignment_groups.create!(
|
139
|
+
:name => 'Training',
|
140
|
+
:expanded_in_library => true
|
141
|
+
)
|
142
|
+
```
|
143
|
+
|
144
|
+
**Required attributes:**
|
145
|
+
name
|
146
|
+
|
147
|
+
**Defaults:**
|
148
|
+
expanded_in_library => true
|
149
|
+
|
150
|
+
**Example queries:**
|
151
|
+
|
152
|
+
```ruby
|
153
|
+
assignment_groups = schedule.assignment_groups
|
154
|
+
|
155
|
+
final_group_name = assignment_groups.last.name
|
156
|
+
expanded_groups = assignment_groups.where(:expanded_in_library => true)
|
157
|
+
visible_assignment_count = expanded_groups.map(&:assignments).map(&:count).inject(:+)
|
158
|
+
```
|
159
|
+
|
160
|
+
## Assignments
|
161
|
+
|
162
|
+
An assignment is a task. An assignment cannot be in multiple assignment groups.
|
163
|
+
|
164
|
+
```ruby
|
165
|
+
training = schedule.assignment_groups.create!(:name => 'Training')
|
166
|
+
training.assignments.create!(
|
167
|
+
:name => 'Rails Conference',
|
168
|
+
:colour => { :red => 1, :green => 0, :blue => 0 }
|
169
|
+
)
|
170
|
+
```
|
171
|
+
|
172
|
+
**Required attributes:**
|
173
|
+
name
|
174
|
+
|
175
|
+
**Defaults:**
|
176
|
+
colour => { :red => 0.5, :green => 0.5, :blue => 0.5 }
|
177
|
+
|
178
|
+
**Aliases:**
|
179
|
+
color => colour
|
180
|
+
|
181
|
+
**Example queries:**
|
182
|
+
|
183
|
+
```ruby
|
184
|
+
assignments = schedule.assignments
|
185
|
+
|
186
|
+
rails_assignments = assignments.where('name like "%rails%"')
|
187
|
+
first_assignment_colour = assignments.first.colour
|
188
|
+
singleton_assignments = assignments.select { |a| a.assignment_group.assignments.count == 1 }
|
189
|
+
```
|
190
|
+
|
191
|
+
## Working Times
|
192
|
+
|
193
|
+
A working time is a relationship between a resource and an assignment. This is equivalent to scheduling an employee on a specific task for a given duration. Assignments are assigned to resources by creating working times.
|
194
|
+
|
195
|
+
```ruby
|
196
|
+
developers = schedule.resource_groups.create!(:name => 'Developers')
|
197
|
+
chris = developers.resources.create!(:name => 'Christopher Patuzzo')
|
198
|
+
|
199
|
+
channel_5 = schedule.assignment_groups.create!(:name => 'Channel 5')
|
200
|
+
the_gadget_show = channel_5.assignments.create!(:name => 'The Gadget Show')
|
201
|
+
|
202
|
+
chris.working_times.create!(
|
203
|
+
:assignment => the_gadget_show,
|
204
|
+
:begin_date => Date.new(2012, 01, 01),
|
205
|
+
:duration => 20,
|
206
|
+
:notes => 'Based in London'
|
207
|
+
)
|
208
|
+
```
|
209
|
+
|
210
|
+
The creation can also be written from the assignment, or directly from the model:
|
211
|
+
|
212
|
+
```ruby
|
213
|
+
the_gadget_show.working_times.create!(
|
214
|
+
:resource => chris,
|
215
|
+
# etc.
|
216
|
+
)
|
217
|
+
|
218
|
+
XTeamSchedule::WorkingTime.create!(
|
219
|
+
:resource => chris,
|
220
|
+
:assignment => the_gadget_show,
|
221
|
+
# etc.
|
222
|
+
)
|
223
|
+
```
|
224
|
+
|
225
|
+
**Required attributes:**
|
226
|
+
begin_date, duration
|
227
|
+
|
228
|
+
**Example queries:**
|
229
|
+
|
230
|
+
```ruby
|
231
|
+
working_times = schedule.working_times
|
232
|
+
|
233
|
+
maximum_duration = working_times.map(&:duration).max.to_s + ' days'
|
234
|
+
recent_working_times = working_times.where('begin_date > ?', Date.new(2012, 01, 01))
|
235
|
+
resources_on_new_projects = recent_working_times.map(&:resource).uniq.map(&:name)
|
236
|
+
```
|
237
|
+
|
238
|
+
## Interface
|
239
|
+
|
240
|
+
A schedule has one interface that is created automatically. The interface is responsible for the display settings of xTeam. Possible values to pass to the granularities constant are: :day, :week, :month and :year
|
241
|
+
|
242
|
+
```ruby
|
243
|
+
schedule.interface.update_attributes!(
|
244
|
+
:display_assignments_name => true,
|
245
|
+
:display_resources_name => false,
|
246
|
+
:display_working_hours => false,
|
247
|
+
:display_resources_pictures => true,
|
248
|
+
:display_total_of_working_hours => false,
|
249
|
+
:display_assignments_notes => true,
|
250
|
+
:display_absences => true,
|
251
|
+
:time_granularity => XTeamSchedule::Interface::TIME_GRANULARITIES[:month]
|
252
|
+
)
|
253
|
+
```
|
254
|
+
|
255
|
+
**Defaults:**
|
256
|
+
The same as the attributes shown above.
|
257
|
+
|
258
|
+
**Aliases:**
|
259
|
+
|
260
|
+
```ruby
|
261
|
+
display_assignment_names => display_assignments_name
|
262
|
+
display_resource_names => display_resources_name
|
263
|
+
display_resource_pictures => display_resources_pictures
|
264
|
+
display_total_working_hours => display_total_of_working_hours
|
265
|
+
display_assignment_notes => display_assignments_notes
|
266
|
+
```
|
267
|
+
|
268
|
+
## Weekly Working Schedule
|
269
|
+
|
270
|
+
The weekly working schedule determines the 'opening hours' of the company. Working days can be accessed directly, or through weekly_working_schedule.
|
271
|
+
|
272
|
+
```ruby
|
273
|
+
# Set all days to start at 9am, and finish at 5pm
|
274
|
+
# Set a lunch break from 12pm - 1pm each day
|
275
|
+
working_days = schedule.working_days
|
276
|
+
working_days.each do |day|
|
277
|
+
day.update_attributes!(
|
278
|
+
:day_begin => '09:00',
|
279
|
+
:day_end => '17:00',
|
280
|
+
:break_begin => '12:00',
|
281
|
+
:break_end => '13:00'
|
282
|
+
)
|
283
|
+
end
|
284
|
+
```
|
285
|
+
|
286
|
+
Non working days are determined by setting the 'day_begin' attribute to nil. This works similarly for days without lunch breaks:
|
287
|
+
|
288
|
+
```ruby
|
289
|
+
wednesday = working_days.find_by_name('Wednesday')
|
290
|
+
saturday = working_days.find_by_name('Saturday')
|
291
|
+
sunday = working_days.find_by_name('Sunday')
|
292
|
+
|
293
|
+
[saturday, sunday].each { |day| day.update_attributes!(:day_begin => nil) }
|
294
|
+
wednesday.update_attribute(:break_begin, nil)
|
295
|
+
```
|
296
|
+
|
297
|
+
**Defaults:**
|
298
|
+
The default weekly working schedule is identical to the same as the one set up above. i.e. 9am-5pm Mon-Fri, with lunch from 12pm-1pm.
|
299
|
+
|
300
|
+
## Under Development
|
301
|
+
|
302
|
+
This gem is far from complete. The following is a list of features that are under development:
|
303
|
+
|
304
|
+
* Resource images
|
305
|
+
* Sort by
|
306
|
+
* Holidays
|
307
|
+
* Absences
|
308
|
+
* Remote access
|
309
|
+
* To assign
|
310
|
+
* Advanced colour controls
|
311
|
+
* Schedule splicing between dates
|
312
|
+
* Advanced image support
|
313
|
+
* Built-in example
|
314
|
+
* Documentation
|
315
|
+
* Binary operators
|
316
|
+
* File system hooks
|
317
|
+
* Date/duration helpers
|
318
|
+
* Generating reports
|
319
|
+
|
320
|
+
## Contribution
|
321
|
+
|
322
|
+
Please feel free to contribute, either through pull requests or feature requests here on Github.
|
323
|
+
|
324
|
+
For news and latest updates, follow me on Twitter ([@cpatuzzo](https://twitter.com/#!/cpatuzzo)).
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class XTeamSchedule
|
2
|
+
|
3
|
+
def initialize(hash_or_filename = nil)
|
4
|
+
if hash_or_filename.present?
|
5
|
+
hash = hash_or_filename.class == Hash ? hash_or_filename : IO.read(hash_or_filename)
|
6
|
+
@schedule = Parser.parse(hash)
|
7
|
+
else
|
8
|
+
@schedule = Schedule.create!
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def write(filename)
|
13
|
+
raise 'No filename provided' unless filename.present?
|
14
|
+
IO.write(hash, filename)
|
15
|
+
end
|
16
|
+
|
17
|
+
def hash
|
18
|
+
Composer.compose(self)
|
19
|
+
end
|
20
|
+
|
21
|
+
def method_missing(*args);
|
22
|
+
@schedule.send(*args)
|
23
|
+
end
|
24
|
+
|
25
|
+
def inspect
|
26
|
+
stats = [:resource_groups, :resources, :assignment_groups, :assignments, :working_times].map { |s| [s, send(s).count] }
|
27
|
+
stats_string = stats.map { |s| "#{s.first}(#{s.second})" }.join(', ')
|
28
|
+
"#<XTeamSchedule #{stats_string}>"
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
class XTeamSchedule::Composer
|
2
|
+
|
3
|
+
attr_accessor :schedule, :hash
|
4
|
+
|
5
|
+
def self.compose(schedule)
|
6
|
+
new(schedule).compose
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(schedule)
|
10
|
+
schedule.save!
|
11
|
+
self.schedule = schedule
|
12
|
+
self.hash = {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def compose
|
16
|
+
compose_resource_groups!
|
17
|
+
compose_resources!
|
18
|
+
compose_assignment_groups!
|
19
|
+
compose_assignments!
|
20
|
+
compose_working_times!
|
21
|
+
compose_interface!
|
22
|
+
compose_weekly_working_schedule!
|
23
|
+
compose_schedule!
|
24
|
+
hash
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def compose_resource_groups!
|
30
|
+
hash['resource groups'] ||= []
|
31
|
+
resource_groups = schedule.resource_groups
|
32
|
+
resource_groups.each do |rg|
|
33
|
+
hash['resource groups'] << {
|
34
|
+
'name' => rg.name,
|
35
|
+
'expanded in library' => rg.expanded_in_library
|
36
|
+
}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def compose_resources!
|
41
|
+
hash['resources'] ||= []
|
42
|
+
resources = schedule.resource_groups.map(&:resources).flatten
|
43
|
+
resources.each do |r|
|
44
|
+
hash['resources'] << {
|
45
|
+
'displayedInPlanning' => r.displayed_in_planning,
|
46
|
+
'email' => r.email,
|
47
|
+
'image' => (StringIO.new(r.image) if r.image),
|
48
|
+
'mobile' => r.mobile,
|
49
|
+
'name' => r.name,
|
50
|
+
'phone' => r.phone,
|
51
|
+
'group' => r.resource_group.name
|
52
|
+
}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def compose_assignment_groups!
|
57
|
+
hash['task categories'] ||= []
|
58
|
+
assignment_groups = schedule.assignment_groups
|
59
|
+
assignment_groups.each do |ag|
|
60
|
+
hash['task categories'] << {
|
61
|
+
'name' => ag.name,
|
62
|
+
'expanded in library' => ag.expanded_in_library
|
63
|
+
}
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def compose_assignments!
|
68
|
+
hash['tasks'] ||= []
|
69
|
+
assignments = schedule.assignment_groups.map(&:assignments).flatten
|
70
|
+
assignments.each do |a|
|
71
|
+
hash['tasks'] << {
|
72
|
+
'name' => a.name,
|
73
|
+
'category' => a.assignment_group.name,
|
74
|
+
'kind' => 0,
|
75
|
+
'color' => compose_colour(a.colour)
|
76
|
+
}
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def compose_working_times!
|
81
|
+
hash['objectsForResources'] ||= {}
|
82
|
+
resources = schedule.resource_groups.map(&:resources).flatten
|
83
|
+
resources.each do |r|
|
84
|
+
working_times_with_parents = r.working_times.select { |wt| wt.resource and wt.assignment }
|
85
|
+
next unless working_times_with_parents.any?
|
86
|
+
hash['objectsForResources'].merge!(r.name => [])
|
87
|
+
working_times_with_parents.each do |wt|
|
88
|
+
hash['objectsForResources'][r.name] << {
|
89
|
+
'task' => wt.assignment.name,
|
90
|
+
'begin date' => compose_date(wt.begin_date),
|
91
|
+
'duration' => wt.duration,
|
92
|
+
'notes' => wt.notes,
|
93
|
+
'title' => ''
|
94
|
+
}
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def compose_interface!
|
100
|
+
interface = schedule.interface
|
101
|
+
hash['display task names'] = interface.display_assignments_name
|
102
|
+
hash['display resource names'] = interface.display_resources_name
|
103
|
+
hash['display worked time'] = interface.display_working_hours
|
104
|
+
hash['display resource icons'] = interface.display_resources_pictures
|
105
|
+
hash['display resource totals'] = interface.display_total_of_working_hours
|
106
|
+
hash['display task notes'] = interface.display_assignments_notes
|
107
|
+
hash['display absence cells'] = interface.display_absences
|
108
|
+
hash['interface status'] = { 'latest time navigation mode' => interface.time_granularity }
|
109
|
+
end
|
110
|
+
|
111
|
+
def compose_weekly_working_schedule!
|
112
|
+
weekly_working_schedule = schedule.weekly_working_schedule
|
113
|
+
working_days = weekly_working_schedule.working_days
|
114
|
+
|
115
|
+
hash['settings'] ||= {}
|
116
|
+
hash['settings']['days off'] ||= []
|
117
|
+
hash['settings']['working schedule'] ||= {}
|
118
|
+
|
119
|
+
working_days.each do |day|
|
120
|
+
day_name = day.name.downcase
|
121
|
+
hash['settings']['working schedule'][day_name] =
|
122
|
+
if day.day_begin.present?
|
123
|
+
{ 'worked' => 'yes',
|
124
|
+
'begin' => compose_time(day.day_begin),
|
125
|
+
'end' => compose_time(day.day_end) }
|
126
|
+
else
|
127
|
+
{ 'worked' => 'no' }
|
128
|
+
end
|
129
|
+
|
130
|
+
hash['settings']['working schedule']["pause_#{day_name}"] =
|
131
|
+
if day.day_begin.present? and day.break_begin.present?
|
132
|
+
{ 'worked' => 'yes',
|
133
|
+
'begin' => compose_time(day.break_begin),
|
134
|
+
'end' => compose_time(day.break_end),
|
135
|
+
'duration' => compose_time(day.break_end) - compose_time(day.break_begin) }
|
136
|
+
else
|
137
|
+
{}
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def compose_schedule!
|
143
|
+
hash['begin date'] = compose_date(schedule.begin_date)
|
144
|
+
hash['end date'] = compose_date(schedule.end_date)
|
145
|
+
end
|
146
|
+
|
147
|
+
def compose_colour(colour_hash)
|
148
|
+
{ 'alpha' => 1 }.merge([:red, :green, :blue].inject({}) { |h, c| h[c.to_s] = colour_hash[c]; h })
|
149
|
+
end
|
150
|
+
|
151
|
+
def compose_date(date)
|
152
|
+
return unless date.present?
|
153
|
+
components = []
|
154
|
+
components << ("%02d" % date.month)
|
155
|
+
components << ("%02d" % date.day)
|
156
|
+
components << date.year
|
157
|
+
components.join('/')
|
158
|
+
end
|
159
|
+
|
160
|
+
def compose_time(time_string)
|
161
|
+
return unless time_string.present?
|
162
|
+
hours, minutes = time_string.split(':').map(&:to_i)
|
163
|
+
|
164
|
+
hours * 60 + minutes
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
class XTeamSchedule::DB
|
2
|
+
|
3
|
+
def self.connect
|
4
|
+
ActiveRecord::Base.establish_connection(
|
5
|
+
:adapter => 'sqlite3',
|
6
|
+
:database => ':memory:'
|
7
|
+
)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.build_schema
|
11
|
+
ActiveRecord::Schema.verbose = false
|
12
|
+
ActiveRecord::Schema.define do
|
13
|
+
|
14
|
+
create_table :schedules do |table|
|
15
|
+
table.column :begin_date, :date, :default => 10.years.ago.to_date
|
16
|
+
table.column :end_date, :date, :default => 10.years.from_now.to_date
|
17
|
+
end
|
18
|
+
|
19
|
+
create_table :interfaces do |table|
|
20
|
+
table.column :schedule_id, :integer
|
21
|
+
table.column :display_assignments_name, :boolean, :default => true
|
22
|
+
table.column :display_resources_name, :boolean, :default => false
|
23
|
+
table.column :display_working_hours, :boolean, :default => false
|
24
|
+
table.column :display_resources_pictures, :boolean, :default => true
|
25
|
+
table.column :display_total_of_working_hours, :boolean, :default => false
|
26
|
+
table.column :display_assignments_notes, :boolean, :default => true
|
27
|
+
table.column :display_absences, :boolean, :default => true
|
28
|
+
table.column :time_granularity, :integer, :default => XTeamSchedule::Interface::TIME_GRANULARITIES[:month]
|
29
|
+
end
|
30
|
+
|
31
|
+
create_table :weekly_working_schedules do |table|
|
32
|
+
table.column :schedule_id, :integer
|
33
|
+
end
|
34
|
+
|
35
|
+
create_table :working_days do |table|
|
36
|
+
table.column :weekly_working_schedule_id, :integer
|
37
|
+
table.column :name, :string
|
38
|
+
table.column :day_begin, :string
|
39
|
+
table.column :day_end, :string
|
40
|
+
table.column :break_begin, :string
|
41
|
+
table.column :break_end, :string
|
42
|
+
end
|
43
|
+
|
44
|
+
create_table :resource_groups do |table|
|
45
|
+
table.column :schedule_id, :integer
|
46
|
+
table.column :expanded_in_library, :boolean, :default => true
|
47
|
+
table.column :name, :string
|
48
|
+
end
|
49
|
+
|
50
|
+
create_table :resources do |table|
|
51
|
+
table.column :resource_group_id, :integer
|
52
|
+
table.column :displayed_in_planning, :boolean, :default => true
|
53
|
+
table.column :email, :string
|
54
|
+
table.column :image, :string
|
55
|
+
table.column :mobile, :string
|
56
|
+
table.column :name, :string
|
57
|
+
table.column :phone, :string
|
58
|
+
end
|
59
|
+
|
60
|
+
create_table :assignment_groups do |table|
|
61
|
+
table.column :schedule_id, :integer
|
62
|
+
table.column :expanded_in_library, :boolean, :default => true
|
63
|
+
table.column :name, :string
|
64
|
+
end
|
65
|
+
|
66
|
+
create_table :assignments do |table|
|
67
|
+
table.column :assignment_group_id, :integer
|
68
|
+
table.column :name, :string
|
69
|
+
table.column :colour, :string
|
70
|
+
end
|
71
|
+
|
72
|
+
create_table :working_times do |table|
|
73
|
+
table.column :resource_id, :integer
|
74
|
+
table.column :assignment_id, :integer
|
75
|
+
table.column :begin_date, :date
|
76
|
+
table.column :duration, :integer
|
77
|
+
table.column :notes, :string
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
class XTeamSchedule::Parser
|
2
|
+
|
3
|
+
attr_accessor :hash, :schedule
|
4
|
+
|
5
|
+
def self.parse(hash)
|
6
|
+
new(hash).parse
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(hash)
|
10
|
+
self.hash = hash
|
11
|
+
self.schedule = XTeamSchedule::Schedule.create!
|
12
|
+
end
|
13
|
+
|
14
|
+
def parse
|
15
|
+
parse_resource_groups!
|
16
|
+
parse_resources!
|
17
|
+
parse_assignment_groups!
|
18
|
+
parse_assignments!
|
19
|
+
parse_working_times!
|
20
|
+
parse_interface!
|
21
|
+
parse_weekly_working_schedule!
|
22
|
+
parse_schedule!
|
23
|
+
schedule
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def parse_resource_groups!
|
29
|
+
hash['resource groups'].try(:each) do |rg|
|
30
|
+
schedule.resource_groups.create!(
|
31
|
+
:name => rg['name'],
|
32
|
+
:expanded_in_library => rg['expanded in library']
|
33
|
+
)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def parse_resources!
|
38
|
+
hash['resources'].try(:each) do |r|
|
39
|
+
resource_group = schedule.resource_groups.find_by_name(r['group'])
|
40
|
+
if resource_group
|
41
|
+
image = r['image'].class == StringIO ? r['image'].read : ''
|
42
|
+
resource_group.resources.create!(
|
43
|
+
:displayed_in_planning => r['displayedInPlanning'],
|
44
|
+
:email => r['email'],
|
45
|
+
:image => image,
|
46
|
+
:mobile => r['mobile'],
|
47
|
+
:name => r['name'],
|
48
|
+
:phone => r['phone']
|
49
|
+
)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def parse_assignment_groups!
|
55
|
+
hash['task categories'].try(:each) do |ag|
|
56
|
+
schedule.assignment_groups.create!(
|
57
|
+
:name => ag['name'],
|
58
|
+
:expanded_in_library => ag['expanded in library']
|
59
|
+
)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def parse_assignments!
|
64
|
+
hash['tasks'].try(:each) do |a|
|
65
|
+
assignment_group = schedule.assignment_groups.find_by_name(a['category'])
|
66
|
+
if assignment_group
|
67
|
+
assignment_group.assignments.create!(
|
68
|
+
:name => a['name'],
|
69
|
+
:colour => parse_colour(a['color'])
|
70
|
+
)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def parse_working_times!
|
76
|
+
resources = schedule.resource_groups.map(&:resources).flatten
|
77
|
+
assignments = schedule.assignment_groups.map(&:assignments).flatten
|
78
|
+
hash['objectsForResources'] ||= {}
|
79
|
+
hash['objectsForResources'].each do |r_name, wt_array|
|
80
|
+
resource = resources.detect { |r| r.name == r_name }
|
81
|
+
next unless resource
|
82
|
+
wt_array.each do |wt|
|
83
|
+
assignment = assignments.detect { |a| a.name == wt['task'] }
|
84
|
+
next unless assignment
|
85
|
+
resource.working_times.create!(
|
86
|
+
:assignment => assignment,
|
87
|
+
:begin_date => parse_date(wt['begin date']),
|
88
|
+
:duration => wt['duration'],
|
89
|
+
:notes => wt['notes']
|
90
|
+
)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def parse_interface!
|
96
|
+
interface_status = hash['interface status']
|
97
|
+
time_granularity = interface_status['latest time navigation mode'] if interface_status.present?
|
98
|
+
schedule.interface.update_attributes!(
|
99
|
+
:display_assignments_name => hash['display task names'],
|
100
|
+
:display_resources_name => hash['display resource names'],
|
101
|
+
:display_working_hours => hash['display worked time'],
|
102
|
+
:display_resources_pictures => hash['display resource icons'],
|
103
|
+
:display_total_of_working_hours => hash['display resource totals'],
|
104
|
+
:display_assignments_notes => hash['display task notes'],
|
105
|
+
:display_absences => hash['display absence cells'],
|
106
|
+
:time_granularity => time_granularity
|
107
|
+
)
|
108
|
+
end
|
109
|
+
|
110
|
+
def parse_weekly_working_schedule!
|
111
|
+
settings = hash['settings']
|
112
|
+
return unless settings.present?
|
113
|
+
working_schedule = settings['working schedule']
|
114
|
+
return unless working_schedule.present?
|
115
|
+
|
116
|
+
weekly_working_schedule = schedule.weekly_working_schedule
|
117
|
+
working_days = weekly_working_schedule.working_days
|
118
|
+
|
119
|
+
working_days.destroy_all
|
120
|
+
XTeamSchedule::WorkingDay::WORKING_DAY_NAMES.each do |name|
|
121
|
+
day = working_schedule[name.downcase]
|
122
|
+
pause = working_schedule["pause_#{name.downcase}"]
|
123
|
+
|
124
|
+
if day.present?
|
125
|
+
day_begin = parse_time(day['begin']) if day['worked'] == 'yes'
|
126
|
+
day_end = parse_time(day['end']) if day_begin
|
127
|
+
end
|
128
|
+
|
129
|
+
if pause.present?
|
130
|
+
break_begin = parse_time(pause['begin']) if pause['worked'] == 'yes'
|
131
|
+
break_end = parse_time(pause['end']) if break_begin
|
132
|
+
end
|
133
|
+
|
134
|
+
working_days << XTeamSchedule::WorkingDay.create!(
|
135
|
+
:name => name,
|
136
|
+
:day_begin => day_begin, :day_end => day_end,
|
137
|
+
:break_begin => break_begin, :break_end => break_end
|
138
|
+
)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def parse_schedule!
|
143
|
+
schedule.update_attributes!(
|
144
|
+
:begin_date => parse_date(hash['begin date']),
|
145
|
+
:end_date => parse_date(hash['end date'])
|
146
|
+
)
|
147
|
+
end
|
148
|
+
|
149
|
+
def parse_colour(colour_data)
|
150
|
+
[:red, :green, :blue].inject({}) { |h, c| h[c] = colour_data[c.to_s]; h }
|
151
|
+
end
|
152
|
+
|
153
|
+
def parse_date(date_string)
|
154
|
+
return unless date_string.present?
|
155
|
+
month, day, year = date_string.split('/').map(&:to_i)
|
156
|
+
Date.new(year, month, day)
|
157
|
+
end
|
158
|
+
|
159
|
+
def parse_time(seconds)
|
160
|
+
return unless seconds.present?
|
161
|
+
hours = seconds / 60
|
162
|
+
minutes = seconds % 60
|
163
|
+
|
164
|
+
hours = "%02d" % hours
|
165
|
+
minutes = "%02d" % minutes
|
166
|
+
[hours, minutes].join(':')
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
class XTeamSchedule::Assignment < ActiveRecord::Base
|
2
|
+
belongs_to :assignment_group
|
3
|
+
has_many :working_times, :dependent => :destroy
|
4
|
+
delegate :schedule, :to => :assignment_group
|
5
|
+
|
6
|
+
validates_presence_of :name
|
7
|
+
validate :uniqueness_of_name_scoped_to_schedule
|
8
|
+
validates_presence_of :colour
|
9
|
+
validate :rgb_colour
|
10
|
+
|
11
|
+
serialize :colour, Hash
|
12
|
+
after_initialize :set_default_colour
|
13
|
+
before_save :symbolize_colour!
|
14
|
+
after_validation :float_colour_values!
|
15
|
+
|
16
|
+
alias_attribute :color, :colour
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def uniqueness_of_name_scoped_to_schedule
|
21
|
+
return unless new_record?
|
22
|
+
assignment_group = self.assignment_group or return
|
23
|
+
schedule = assignment_group.schedule or return
|
24
|
+
assignments = schedule.assignments or return
|
25
|
+
|
26
|
+
if assignments.find_by_name(name).present?
|
27
|
+
errors.add(:name, 'must be unique within the schedule')
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def set_default_colour
|
32
|
+
self.colour = { :red => 0.5, :green => 0.5, :blue => 0.5 } if colour.empty?
|
33
|
+
end
|
34
|
+
|
35
|
+
def symbolize_colour!
|
36
|
+
self.colour = colour.inject({}) { |h, (k, v)| h[k.to_sym] = v; h }
|
37
|
+
end
|
38
|
+
|
39
|
+
def float_colour_values!
|
40
|
+
self.colour = colour.inject({}) { |h, (k, v)| h[k] = v.to_f; h } if colour.class == Hash
|
41
|
+
end
|
42
|
+
|
43
|
+
def rgb_colour
|
44
|
+
is_out_of_range = proc { |c| f = Float(c); f < 0 || f > 1 }
|
45
|
+
raise 'invalid' if [:red, :green, :blue].any? { |c| is_out_of_range[colour[c]] }
|
46
|
+
raise 'invalid' if colour.count != 3
|
47
|
+
rescue
|
48
|
+
errors.add(:colour, 'is not a valid rgb hash')
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class XTeamSchedule::Interface < ActiveRecord::Base
|
2
|
+
belongs_to :schedule
|
3
|
+
|
4
|
+
alias_attribute :display_assignment_names, :display_assignments_name
|
5
|
+
alias_attribute :display_resource_names, :display_resources_name
|
6
|
+
alias_attribute :display_resource_pictures, :display_resources_pictures
|
7
|
+
alias_attribute :display_total_working_hours, :display_total_of_working_hours
|
8
|
+
alias_attribute :display_assignment_notes, :display_assignments_notes
|
9
|
+
|
10
|
+
TIME_GRANULARITIES = { :day => 4, :week => 2, :month => 1, :year => 0 }
|
11
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class XTeamSchedule::Resource < ActiveRecord::Base
|
2
|
+
belongs_to :resource_group
|
3
|
+
has_many :working_times, :dependent => :destroy
|
4
|
+
delegate :schedule, :to => :resource_group
|
5
|
+
|
6
|
+
validates_presence_of :name
|
7
|
+
validate :uniqueness_of_name_scoped_to_schedule
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def uniqueness_of_name_scoped_to_schedule
|
12
|
+
return unless new_record?
|
13
|
+
resource_group = self.resource_group or return
|
14
|
+
schedule = resource_group.schedule or return
|
15
|
+
resources = schedule.resources or return
|
16
|
+
|
17
|
+
if resources.find_by_name(name).present?
|
18
|
+
errors.add(:name, 'must be unique within the schedule')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class XTeamSchedule::Schedule < ActiveRecord::Base
|
2
|
+
has_many :resource_groups, :dependent => :destroy
|
3
|
+
has_many :resources, :through => :resource_groups
|
4
|
+
has_many :assignment_groups, :dependent => :destroy
|
5
|
+
has_many :assignments, :through => :assignment_groups
|
6
|
+
has_many :working_times, :through => :resources
|
7
|
+
has_many :working_days, :through => :weekly_working_schedule
|
8
|
+
|
9
|
+
has_one :interface
|
10
|
+
after_initialize :set_default_interface
|
11
|
+
|
12
|
+
has_one :weekly_working_schedule
|
13
|
+
after_initialize :set_default_weekly_working_schedule
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def set_default_interface
|
18
|
+
self.interface ||= XTeamSchedule::Interface.new
|
19
|
+
end
|
20
|
+
|
21
|
+
def set_default_weekly_working_schedule
|
22
|
+
self.weekly_working_schedule = XTeamSchedule::WeeklyWorkingSchedule.new
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class XTeamSchedule::WeeklyWorkingSchedule < ActiveRecord::Base
|
2
|
+
belongs_to :schedule
|
3
|
+
has_many :working_days, :dependent => :destroy
|
4
|
+
|
5
|
+
after_initialize :set_default_working_days
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def set_default_working_days
|
10
|
+
return unless self.working_days.empty?
|
11
|
+
day_names = XTeamSchedule::WorkingDay::WORKING_DAY_NAMES
|
12
|
+
weekdays = day_names[0..4]
|
13
|
+
day_names.each_with_index do |name, i|
|
14
|
+
self.working_days << XTeamSchedule::WorkingDay.new(
|
15
|
+
:name => name,
|
16
|
+
:day_begin => (weekdays.shift.present? ? '09:00' : nil),
|
17
|
+
:day_end => '17:00',
|
18
|
+
:break_begin => '12:00',
|
19
|
+
:break_end => '13:00'
|
20
|
+
)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class XTeamSchedule::WorkingDay < ActiveRecord::Base
|
2
|
+
belongs_to :weekly_working_schedule
|
3
|
+
delegate :schedule, :to => :weekly_working_schedule
|
4
|
+
|
5
|
+
validate :format_of_times
|
6
|
+
|
7
|
+
WORKING_DAY_NAMES = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def format_of_times
|
12
|
+
time_format = /\d{2}:\d{2}/
|
13
|
+
[:day_begin, :day_end, :break_begin, :break_end].each do |sym|
|
14
|
+
time = send(sym)
|
15
|
+
return if sym == :day_begin and time.nil?
|
16
|
+
return if sym == :break_begin and time.nil?
|
17
|
+
unless time =~ time_format
|
18
|
+
errors.add(sym, "is not a valid 24-hour time, must be hh:mm format: #{time}")
|
19
|
+
next
|
20
|
+
end
|
21
|
+
hours, minutes = time.split(':').map(&:to_i)
|
22
|
+
if hours > 23 or minutes > 59
|
23
|
+
errors.add(sym, "is not a valid 24-hour time, must be hh:mm format: #{time}")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
class XTeamSchedule::WorkingTime < ActiveRecord::Base
|
2
|
+
belongs_to :resource
|
3
|
+
belongs_to :assignment
|
4
|
+
delegate :resource_group, :to => :resource
|
5
|
+
delegate :assignment_group, :to => :assignment
|
6
|
+
delegate :schedule, :to => :resource
|
7
|
+
|
8
|
+
validates_presence_of :begin_date, :duration
|
9
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'plist'
|
2
|
+
require 'active_record'
|
3
|
+
require 'sqlite3'
|
4
|
+
require 'xteam_schedule/core'
|
5
|
+
|
6
|
+
require 'xteam_schedule/models/assignment'
|
7
|
+
require 'xteam_schedule/models/assignment_group'
|
8
|
+
require 'xteam_schedule/models/interface'
|
9
|
+
require 'xteam_schedule/models/resource'
|
10
|
+
require 'xteam_schedule/models/resource_group'
|
11
|
+
require 'xteam_schedule/models/schedule'
|
12
|
+
require 'xteam_schedule/models/weekly_working_schedule'
|
13
|
+
require 'xteam_schedule/models/working_day'
|
14
|
+
require 'xteam_schedule/models/working_time'
|
15
|
+
|
16
|
+
require 'xteam_schedule/facilitation/composer'
|
17
|
+
require 'xteam_schedule/facilitation/db'
|
18
|
+
require 'xteam_schedule/facilitation/io'
|
19
|
+
require 'xteam_schedule/facilitation/parser'
|
20
|
+
|
21
|
+
XTeamSchedule::DB.connect
|
22
|
+
XTeamSchedule::DB.build_schema
|
metadata
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: xteam_schedule
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Christopher Patuzzo
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-02-15 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: plist
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 3
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
version: "0"
|
32
|
+
type: :runtime
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: activerecord
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
hash: 3
|
43
|
+
segments:
|
44
|
+
- 0
|
45
|
+
version: "0"
|
46
|
+
type: :runtime
|
47
|
+
version_requirements: *id002
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: sqlite3
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
hash: 3
|
57
|
+
segments:
|
58
|
+
- 0
|
59
|
+
version: "0"
|
60
|
+
type: :runtime
|
61
|
+
version_requirements: *id003
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rspec
|
64
|
+
prerelease: false
|
65
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
hash: 3
|
71
|
+
segments:
|
72
|
+
- 0
|
73
|
+
version: "0"
|
74
|
+
type: :development
|
75
|
+
version_requirements: *id004
|
76
|
+
- !ruby/object:Gem::Dependency
|
77
|
+
name: factory_girl
|
78
|
+
prerelease: false
|
79
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
80
|
+
none: false
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
hash: 3
|
85
|
+
segments:
|
86
|
+
- 0
|
87
|
+
version: "0"
|
88
|
+
type: :development
|
89
|
+
version_requirements: *id005
|
90
|
+
- !ruby/object:Gem::Dependency
|
91
|
+
name: database_cleaner
|
92
|
+
prerelease: false
|
93
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
hash: 3
|
99
|
+
segments:
|
100
|
+
- 0
|
101
|
+
version: "0"
|
102
|
+
type: :development
|
103
|
+
version_requirements: *id006
|
104
|
+
description: Full control over schedules for use with adnX's xTeam software
|
105
|
+
email: chris.patuzzo@gmail.com
|
106
|
+
executables: []
|
107
|
+
|
108
|
+
extensions: []
|
109
|
+
|
110
|
+
extra_rdoc_files: []
|
111
|
+
|
112
|
+
files:
|
113
|
+
- README.md
|
114
|
+
- lib/xteam_schedule/core.rb
|
115
|
+
- lib/xteam_schedule/facilitation/composer.rb
|
116
|
+
- lib/xteam_schedule/facilitation/db.rb
|
117
|
+
- lib/xteam_schedule/facilitation/io.rb
|
118
|
+
- lib/xteam_schedule/facilitation/parser.rb
|
119
|
+
- lib/xteam_schedule/models/assignment.rb
|
120
|
+
- lib/xteam_schedule/models/assignment_group.rb
|
121
|
+
- lib/xteam_schedule/models/interface.rb
|
122
|
+
- lib/xteam_schedule/models/resource.rb
|
123
|
+
- lib/xteam_schedule/models/resource_group.rb
|
124
|
+
- lib/xteam_schedule/models/schedule.rb
|
125
|
+
- lib/xteam_schedule/models/weekly_working_schedule.rb
|
126
|
+
- lib/xteam_schedule/models/working_day.rb
|
127
|
+
- lib/xteam_schedule/models/working_time.rb
|
128
|
+
- lib/xteam_schedule.rb
|
129
|
+
homepage: https://github.com/cpatuzzo/xteam_schedule
|
130
|
+
licenses: []
|
131
|
+
|
132
|
+
post_install_message:
|
133
|
+
rdoc_options: []
|
134
|
+
|
135
|
+
require_paths:
|
136
|
+
- lib
|
137
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
138
|
+
none: false
|
139
|
+
requirements:
|
140
|
+
- - ">="
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
hash: 3
|
143
|
+
segments:
|
144
|
+
- 0
|
145
|
+
version: "0"
|
146
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
147
|
+
none: false
|
148
|
+
requirements:
|
149
|
+
- - ">="
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
hash: 3
|
152
|
+
segments:
|
153
|
+
- 0
|
154
|
+
version: "0"
|
155
|
+
requirements: []
|
156
|
+
|
157
|
+
rubyforge_project:
|
158
|
+
rubygems_version: 1.8.15
|
159
|
+
signing_key:
|
160
|
+
specification_version: 3
|
161
|
+
summary: XTeam Schedule
|
162
|
+
test_files: []
|
163
|
+
|