task-manager 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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