task-manager 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (28) hide show
  1. data/README.md +136 -1
  2. data/Rakefile +3 -17
  3. data/app/assets/javascripts/task-manager/{application.js → extjs.js} +3 -9
  4. data/app/assets/javascripts/task-manager/extjs/app/controller/Plans.js +29 -264
  5. data/app/assets/javascripts/task-manager/extjs/app/helper/{application_helper.js → ApplicationHelper.js} +0 -0
  6. data/app/assets/javascripts/task-manager/extjs/app/model/Assignee.js +16 -0
  7. data/app/assets/javascripts/task-manager/extjs/app/model/Plan.js +18 -156
  8. data/app/assets/javascripts/task-manager/extjs/app/store/Assignees.js +37 -30
  9. data/app/assets/javascripts/task-manager/extjs/app/view/assignee/TreeCombo.js +48 -0
  10. data/app/assets/javascripts/task-manager/extjs/app/view/plan/Form.js +290 -0
  11. data/app/assets/javascripts/task-manager/extjs/app/view/plan/{Window.js → FormWindow.js} +7 -5
  12. data/app/assets/javascripts/task-manager/extjs/lib/ux/Model.js +14 -0
  13. data/app/assets/javascripts/task-manager/extjs/lib/ux/TreeCombo.js +276 -0
  14. data/app/controllers/task_manager/api/v1/plans_controller.rb +3 -3
  15. data/app/controllers/task_manager/api/v1/tasks_controller.rb +1 -1
  16. data/app/models/task_manager/plan.rb +48 -37
  17. data/lib/task-manager/deadline_validator.rb +23 -14
  18. data/lib/task-manager/version.rb +1 -1
  19. metadata +27 -16
  20. data/app/assets/javascripts/task-manager/extjs/app/store/AssigneesTree.js +0 -3
  21. data/app/assets/javascripts/task-manager/extjs/app/view/plan/AssignablesWindow.js +0 -23
  22. data/app/assets/javascripts/task-manager/extjs/app/view/plan/Edit.js +0 -189
  23. data/app/assets/javascripts/task-manager/extjs/app/view/plan/EditWindow.js +0 -21
  24. data/app/assets/javascripts/task-manager/extjs/app/view/plan/New.js +0 -190
  25. data/app/assets/javascripts/task-manager/extjs/app/view/plan/SelectAssignables.js +0 -22
  26. data/app/assets/javascripts/task-manager/extjs/app/view/plan/SelectAssignablesGrid.js +0 -22
  27. data/app/assets/javascripts/task-manager/extjs/app/view/plan/SelectAssignablesTree.js +0 -9
  28. data/app/assets/stylesheets/task-manager/application.css.scss +0 -13
@@ -1,9 +1,11 @@
1
- Ext.define('TM.view.plan.Window', {
1
+ Ext.define('TM.view.plan.FormWindow', {
2
2
  extend: 'Ext.window.Window',
3
- requires: ['TM.view.plan.New'],
4
- xtype: 'plan_window',
3
+ xtype: 'plan_formwindow',
4
+
5
+ requires: [
6
+ 'TM.view.plan.Form'
7
+ ],
5
8
 
6
- title: '添加计划',
7
9
  modal: true,
8
10
 
9
11
  width: 600,
@@ -15,7 +17,7 @@ Ext.define('TM.view.plan.Window', {
15
17
  },
16
18
 
17
19
  items: [{
18
- xtype: 'plan_new',
20
+ xtype: 'plan_form',
19
21
  border: false
20
22
  }]
21
23
  });
@@ -0,0 +1,14 @@
1
+ Ext.define('Ext.ux.Model', {
2
+ override: 'Ext.data.Model',
3
+
4
+ getPersistData: function() {
5
+ var data = {};
6
+ Ext.Array.each(this.fields.items, function(f) {
7
+ if(f.persist) {
8
+ data[f.name] = this.get(f.name);
9
+ }
10
+ }, this);
11
+
12
+ return data;
13
+ }
14
+ });
@@ -0,0 +1,276 @@
1
+ /*
2
+ Tree combo
3
+ Use with 'Ext.data.TreeStore'
4
+
5
+ If store root note has 'checked' property tree combo becomes multiselect combo (tree store must have records with 'checked' property)
6
+
7
+ Has event 'itemclick' that can be used to capture click
8
+
9
+ Options:
10
+ selectChildren - if set true and if store isn't multiselect, clicking on an non-leaf node selects all it's children
11
+ canSelectFolders - if set true and store isn't multiselect clicking on a folder selects that folder also as a value
12
+
13
+ Use:
14
+
15
+ single leaf node selector:
16
+ selectChildren: false
17
+ canSelectFolders: false
18
+ - this will select only leaf nodes and will not allow selecting non-leaf nodes
19
+
20
+ single node selector (can select leaf and non-leaf nodes)
21
+ selectChildren: false
22
+ canSelectFolders: true
23
+ - this will select single value either leaf or non-leaf
24
+
25
+ children selector:
26
+ selectChildren: true
27
+ canSelectFolders: true
28
+ - clicking on a node will select it's children and node, clicking on a leaf node will select only that node
29
+
30
+ This config:
31
+ selectChildren: true
32
+ canSelectFolders: false
33
+ - is invalid, you cannot select children without node
34
+
35
+ */
36
+ Ext.define('Ext.ux.TreeCombo',
37
+ {
38
+ extend: 'Ext.form.field.Picker',
39
+ alias: 'widget.treecombo',
40
+ tree: false,
41
+ constructor: function(config)
42
+ {
43
+ this.addEvents(
44
+ {
45
+ "itemclick" : true
46
+ });
47
+
48
+ this.listeners = config.listeners;
49
+ this.callParent(arguments);
50
+ },
51
+ records: [],
52
+ recursiveRecords: [],
53
+ selectChildren: true,
54
+ canSelectFolders: true,
55
+ multiselect: false,
56
+ displayField: 'text',
57
+ valueField: 'id',
58
+ recursivePush: function(node)
59
+ {
60
+ var me = this;
61
+ me.recursiveRecords.push(node);
62
+
63
+ node.eachChild(function(nodesingle)
64
+ {
65
+ if(nodesingle.hasChildNodes() == true)
66
+ {
67
+ me.recursivePush(nodesingle);
68
+ }
69
+ else me.recursiveRecords.push(nodesingle);
70
+ });
71
+ },
72
+ recursiveUnPush: function(node)
73
+ {
74
+ var me = this;
75
+ Ext.Array.remove(me.records, node);
76
+
77
+ node.eachChild(function(nodesingle)
78
+ {
79
+ if(nodesingle.hasChildNodes() == true)
80
+ {
81
+ me.recursiveUnPush(nodesingle);
82
+ }
83
+ else Ext.Array.remove(me.records, nodesingle);
84
+ });
85
+ },
86
+ afterLoadSetValue: false,
87
+ setValue: function(valueInit)
88
+ {
89
+ if(typeof valueInit == 'undefined') return;
90
+
91
+ var me = this,
92
+ tree = this.tree,
93
+ value = valueInit.split(',');
94
+
95
+ inputEl = me.inputEl;
96
+
97
+ if(tree.store.isLoading())
98
+ {
99
+ me.afterLoadSetValue = valueInit;
100
+ }
101
+
102
+ if(inputEl && me.emptyText && !Ext.isEmpty(value))
103
+ {
104
+ inputEl.removeCls(me.emptyCls);
105
+ }
106
+
107
+ if(tree == false) return false;
108
+
109
+ var node = tree.getRootNode();
110
+ if(node == null) return false;
111
+
112
+ me.recursiveRecords = [];
113
+ me.recursivePush(node);
114
+
115
+ var valueFin = [];
116
+ var idsFin = [];
117
+
118
+ if(me.multiselect == true)
119
+ {
120
+ Ext.each(me.recursiveRecords, function(record)
121
+ {
122
+ record.set('checked', false);
123
+ });
124
+ }
125
+
126
+ me.records = [];
127
+ Ext.each(me.recursiveRecords, function(record)
128
+ {
129
+ var data = record.get(me.valueField);
130
+ Ext.each(value, function(val)
131
+ {
132
+ if(data == val)
133
+ {
134
+ valueFin.push(record.get(me.displayField));
135
+ idsFin.push(data);
136
+ if(me.multiselect == true) record.set('checked', true);
137
+ me.records.push(record);
138
+ }
139
+ });
140
+ });
141
+
142
+ me.value = valueInit;
143
+ me.setRawValue(valueFin.join(', '));
144
+
145
+ me.checkChange();
146
+ me.applyEmptyText();
147
+ return me;
148
+ },
149
+ getValue: function()
150
+ {
151
+ return this.value;
152
+ },
153
+ getSubmitValue: function()
154
+ {
155
+ return this.value;
156
+ },
157
+ checkParentNodes: function(node)
158
+ {
159
+ if(node == null) return;
160
+
161
+ var me = this,
162
+ checkedAll = true,
163
+ ids = [];
164
+
165
+ Ext.each(me.records, function(value)
166
+ {
167
+ ids.push(value.get(me.valueField));
168
+ });
169
+
170
+ node.eachChild(function(nodesingle)
171
+ {
172
+ if(!Ext.Array.contains(ids, nodesingle.get(me.valueField))) checkedAll = false;
173
+ });
174
+
175
+ if(checkedAll == true)
176
+ {
177
+ me.records.push(node);
178
+ me.checkParentNodes(node.parentNode);
179
+ }
180
+ else
181
+ {
182
+ Ext.Array.remove(me.records, node);
183
+ me.checkParentNodes(node.parentNode);
184
+ }
185
+ },
186
+ initComponent: function()
187
+ {
188
+ var me = this;
189
+
190
+ me.tree = Ext.create('Ext.tree.Panel',
191
+ {
192
+ alias: 'widget.assetstree',
193
+ hidden: true,
194
+ minHeight: 300,
195
+ rootVisible: (typeof me.rootVisible != 'undefined') ? me.rootVisible : true,
196
+ floating: true,
197
+ useArrows: true,
198
+ store: me.store,
199
+ listeners:
200
+ {
201
+ load: function(store, records)
202
+ {
203
+ if(me.afterLoadSetValue != false)
204
+ {
205
+ me.setValue(me.afterLoadSetValue);
206
+ }
207
+ },
208
+ itemclick: function(view, record, item, index, e, eOpts)
209
+ {
210
+ var values = [];
211
+
212
+ var node = me.tree.getRootNode().findChild('id', record.get(me.valueField), true);
213
+ if(node == null)
214
+ {
215
+ if(me.tree.getRootNode().get(me.valueField) == record.get(me.valueField)) node = me.tree.getRootNode();
216
+ else return false;
217
+ }
218
+
219
+ if(me.multiselect == false) me.records = [];
220
+
221
+ if(me.canSelectFolders == false && record.get('leaf') == false) return false;
222
+ if(record.get('leaf') == true || me.selectChildren == false)
223
+ {
224
+ if(me.multiselect == false) me.records.push(record);
225
+ else
226
+ {
227
+ if(record.get('checked') == false) me.records.push(record);
228
+ else Ext.Array.remove(me.records, record);
229
+ }
230
+ }
231
+ else
232
+ {
233
+ me.recursiveRecords = [];
234
+ if(me.multiselect == false || record.get('checked') == false)
235
+ {
236
+ me.recursivePush(node);
237
+ Ext.each(me.recursiveRecords, function(value)
238
+ {
239
+ if(!Ext.Array.contains(me.records, value)) me.records.push(value);
240
+ });
241
+ }
242
+ else if(record.get('checked') == true)
243
+ {
244
+ me.recursiveUnPush(node);
245
+ }
246
+ }
247
+
248
+ if(me.canSelectFolders == true) me.checkParentNodes(node.parentNode);
249
+
250
+ Ext.each(me.records, function(record)
251
+ {
252
+ values.push(record.get(me.valueField));
253
+ });
254
+
255
+ me.setValue(values.join(','));
256
+
257
+ me.fireEvent('itemclick', me, record, item, index, e, eOpts, me.records, values);
258
+
259
+ if(me.multiselect == false) me.onTriggerClick();
260
+ }
261
+ }
262
+ });
263
+
264
+ if(me.tree.getRootNode().get('checked') != null) me.multiselect = true;
265
+
266
+ this.createPicker = function()
267
+ {
268
+ var me = this;
269
+ return me.tree;
270
+ };
271
+
272
+ this.callParent(arguments);
273
+ }
274
+ });
275
+
276
+ //@ sourceURL=/ux/TreeCombo.js
@@ -47,7 +47,7 @@ module TaskManager
47
47
  # }, ...]
48
48
  # }
49
49
  def index
50
- plans = TaskManager::Plan.search(params[:q]).result
50
+ plans = TaskManager::Plan.search(params[:q]).result.order('id DESC')
51
51
  result = {
52
52
  total: plans.count,
53
53
  plans: ActiveModel::ArraySerializer.new(
@@ -84,7 +84,7 @@ module TaskManager
84
84
  # "deadline_minute": ... # 必填
85
85
  # },
86
86
  # "enabled_at": ..., # 必填
87
- # "begin_to_remind": ..., # 必填且小于等于0
87
+ # "begin_to_remind": ..., # 必填且大于等于0
88
88
  # "autocompletable": ..., # 必填,缺省值为false
89
89
  # "assignables_attributes": [{ # 至少需要一个assignable
90
90
  # "assignee_id": ...,
@@ -162,7 +162,7 @@ module TaskManager
162
162
  # "deadline_minute": ... # 必填
163
163
  # },
164
164
  # "enabled_at": ..., # 必填
165
- # "begin_to_remind": ..., # 必填且小于等于0
165
+ # "begin_to_remind": ..., # 必填且大于等于0
166
166
  # "autocompletable": ..., # 必填,缺省值为false
167
167
  #
168
168
  # "assignables_attributes": [{
@@ -45,7 +45,7 @@ module TaskManager
45
45
  # }, ...]
46
46
  # }
47
47
  def index
48
- tasks = TaskManager::Task.search(params[:q]).result
48
+ tasks = TaskManager::Task.search(params[:q]).result.order('id DESC')
49
49
  result = {
50
50
  total: tasks.count,
51
51
  tasks: ActiveModel::ArraySerializer.new(
@@ -1,6 +1,5 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  module TaskManager
3
- # TODO 验证data的合法性
4
3
  class Plan < ActiveRecord::Base
5
4
  include TaskManager::DeadlineCalculator
6
5
  extend Enumerize
@@ -24,7 +23,7 @@ module TaskManager
24
23
  validates_numericality_of :begin_to_remind, greater_than_or_equal_to: 0
25
24
 
26
25
  # validate the deadline
27
- validates :data, deadline: true
26
+ validates :data, presence: true, deadline: true
28
27
 
29
28
  def assignees
30
29
  assignables.collect(&:assignee)
@@ -38,39 +37,10 @@ module TaskManager
38
37
  #
39
38
  # 为每一个计划的执行者创建一个计划任务。
40
39
  def generate_tasks
41
- now = Time.now
42
- default_deadline = case plan_type.to_sym
43
- when :daily then now.end_of_day
44
- when :weekly then now.end_of_week
45
- when :monthly then now.end_of_month
46
- when :quarterly then now.end_of_quarter
47
- when :yearly then now.end_of_year
48
- end
49
-
50
- #reminding_at = default_deadline.ago(-(begin_to_remind * 60))
51
- reminding_at = default_deadline.ago(begin_to_remind * 60)
52
- status = autocompletable ? :finished : :new
53
- finished_at = autocompletable ? Time.now : nil
54
-
55
40
  tasks = []
56
- data.symbolize_keys!
57
-
58
41
  Plan.transaction do
59
42
  assignables.each do |a|
60
- tasks << Task.create! do |t|
61
- t.name = name
62
- t.data = { x: data[:x], y: data[:y] }
63
- t.task_type = plan_type
64
- t.deadline = calculate_deadline(plan_type, data)
65
- t.reminding_at = reminding_at
66
- t.status = status
67
- t.finished_at = finished_at
68
- t.create_assignable(
69
- assignee_id: a.assignee_id,
70
- assignee_type: a.assignee_type,
71
- )
72
- t.callables = callables
73
- end
43
+ tasks << generate_task_for_assignable(a)
74
44
  end
75
45
  end
76
46
 
@@ -79,6 +49,43 @@ module TaskManager
79
49
  tasks
80
50
  end
81
51
 
52
+ def generate_task_for_assignable(a)
53
+ data.symbolize_keys!
54
+
55
+ reminding_at = default_deadline.ago(begin_to_remind * 60)
56
+ if autocompletable
57
+ status, finished_at = :finished, Time.now
58
+ else
59
+ status, finished_at = :new, nil
60
+ end
61
+
62
+ Task.create! do |t|
63
+ t.name = name
64
+ t.data = { x: data[:x], y: data[:y] }
65
+ t.task_type = plan_type
66
+ t.deadline = calculate_deadline(plan_type, data)
67
+ t.reminding_at = reminding_at
68
+ t.status = status
69
+ t.finished_at = finished_at
70
+ t.create_assignable(
71
+ assignee_id: a.assignee_id,
72
+ assignee_type: a.assignee_type,
73
+ )
74
+ t.callables = callables
75
+ end
76
+ end
77
+
78
+ def default_deadline
79
+ now = Time.now
80
+ case plan_type.to_sym
81
+ when :daily then now.end_of_day
82
+ when :weekly then now.end_of_week
83
+ when :monthly then now.end_of_month
84
+ when :quarterly then now.end_of_quarter
85
+ when :yearly then now.end_of_year
86
+ end
87
+ end
88
+
82
89
  class << self
83
90
  # 返回所有需要创建新任务的计划
84
91
  #
@@ -91,9 +98,14 @@ module TaskManager
91
98
  # 2.4) 季计划:`Time.now.beginning_of_quarter <= last_task_created_at <= Time.now.end_of_quarter`
92
99
  # 2.5) 年计划:`Time.now.beginning_of_year <= last_task_created_at <= Time.now.end_of_year`
93
100
  def active
94
- active_by_type(:daily) | active_by_type(:weekly) |
95
- active_by_type(:monthly) | active_by_type(:quarterly) |
96
- active_by_type(:yearly)
101
+ where{
102
+ sift(:active_by_type, 'daily') |
103
+ sift(:active_by_type, 'weekly') |
104
+ sift(:active_by_type, 'monthly') |
105
+ sift(:active_by_type, 'quarterly') |
106
+ sift(:active_by_type, 'yearly')
107
+
108
+ }
97
109
  end
98
110
 
99
111
  def active_by_type(type)
@@ -106,8 +118,7 @@ module TaskManager
106
118
  when :yearly then now.beginning_of_year
107
119
  end
108
120
 
109
- where("plan_type = ? AND last_task_created_at <= ?",
110
- type, beginning_at).where("enabled_at <= ?", now)
121
+ squeel{ (plan_type == type) & (last_task_created_at <= beginning_at) & (enabled_at <= now) }
111
122
  end
112
123
  end
113
124
  end