erp_work_effort 4.0.0 → 4.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/api/v1/projects_controller.rb +25 -0
  3. data/app/controllers/api/v1/time_entries_controller.rb +313 -0
  4. data/app/controllers/api/v1/transportation_routes_controller.rb +9 -0
  5. data/app/controllers/api/v1/work_effort_associations_controller.rb +102 -0
  6. data/app/controllers/api/v1/work_effort_party_assignments_controller.rb +136 -0
  7. data/app/controllers/api/v1/work_effort_types_controller.rb +120 -0
  8. data/app/controllers/api/v1/work_efforts_controller.rb +284 -0
  9. data/app/models/associated_transportation_route.rb +8 -5
  10. data/app/models/cal_evt_party_role.rb +9 -0
  11. data/app/models/calendar_event.rb +46 -0
  12. data/app/models/calendar_invite.rb +10 -0
  13. data/app/models/candidate_submission.rb +37 -0
  14. data/app/models/experience.rb +11 -0
  15. data/app/models/extensions/biz_txn_events.rb +35 -0
  16. data/app/models/extensions/finacial_txns.rb +27 -0
  17. data/app/models/extensions/order_line_item.rb +12 -0
  18. data/app/models/extensions/party.rb +204 -0
  19. data/app/models/party_skill.rb +2 -0
  20. data/app/models/pay_period.rb +59 -0
  21. data/app/models/position.rb +8 -0
  22. data/app/models/position_fulfillment.rb +8 -0
  23. data/app/models/position_type.rb +12 -0
  24. data/app/models/project.rb +59 -0
  25. data/app/models/requirement.rb +2 -0
  26. data/app/models/resume.rb +11 -0
  27. data/app/models/shift.rb +22 -0
  28. data/app/models/skill_type.rb +8 -0
  29. data/app/models/staffing_position.rb +9 -0
  30. data/app/models/time_entry.rb +260 -0
  31. data/app/models/timesheet.rb +194 -0
  32. data/app/models/timesheet_party_role.rb +21 -0
  33. data/app/models/transportation_route.rb +103 -18
  34. data/app/models/transportation_route_segment.rb +44 -0
  35. data/app/models/transportation_route_stop.rb +19 -0
  36. data/app/models/wc_code.rb +7 -0
  37. data/app/models/work_effort.rb +512 -30
  38. data/app/models/work_effort_association.rb +7 -0
  39. data/app/models/work_effort_association_type.rb +5 -0
  40. data/app/models/work_effort_biz_txn_event.rb +22 -0
  41. data/app/models/work_effort_party_assignment.rb +140 -3
  42. data/app/models/work_effort_type.rb +5 -0
  43. data/config/routes.rb +54 -11
  44. data/db/data_migrations/20150709053041_add_skill_types.rb +27 -0
  45. data/db/data_migrations/20150709055922_add_position_types.rb +27 -0
  46. data/db/data_migrations/20150711220000_add_task_tracked_statuses.rb +25 -0
  47. data/db/data_migrations/20150712081804_add_work_effort_types.rb +23 -0
  48. data/db/data_migrations/20150718052204_add_project_tracked_statuses.rb +24 -0
  49. data/db/data_migrations/20150718054404_add_project_party_role_types.rb +14 -0
  50. data/db/data_migrations/20150812150320_add_work_effort_dependency_types.rb +35 -0
  51. data/db/migrate/20100220000000_base_work_efforts.rb +310 -89
  52. data/db/migrate/20150227174108_create_staffing_positions.rb +11 -0
  53. data/db/migrate/20150305194158_create_wc_codes.rb +24 -0
  54. data/db/migrate/20150313134411_create_shifts.rb +28 -0
  55. data/db/migrate/20150325195749_create_candidate_submissions.rb +22 -0
  56. data/db/migrate/20150327115910_resumes.rb +15 -0
  57. data/db/migrate/20150401060938_add_resume_parser_statuses.rb +14 -0
  58. data/db/migrate/20150607181734_create_calendar_events.rb +24 -0
  59. data/db/migrate/20150616174228_create_cal_evt_party_roles.rb +19 -0
  60. data/db/migrate/20150707164520_create_calendar_invites.rb +21 -0
  61. data/db/migrate/20150718052404_create_projects.rb +20 -0
  62. data/db/migrate/20150812191812_update_work_efforts.rb +59 -0
  63. data/db/migrate/20150824152639_add_sequence_to_work_effort.rb +9 -0
  64. data/db/migrate/20150830133951_add_work_effort_biz_txn_events.rb +21 -0
  65. data/db/migrate/20150908200612_update_time_entries.rb +70 -0
  66. data/db/migrate/20151115180427_add_defaults_for_work_effort.rb +36 -0
  67. data/db/migrate/20151218195330_update_transportation_routes.rb +72 -0
  68. data/db/migrate/20160310163052_add_created_by_updated_by_to_erp_work_effort.rb +63 -0
  69. data/db/migrate/20160418115227_add_created_by_updated_by_to_skill_type.rb +34 -0
  70. data/lib/erp_work_effort.rb +3 -0
  71. data/lib/erp_work_effort/config.rb +33 -0
  72. data/lib/erp_work_effort/engine.rb +0 -4
  73. data/lib/erp_work_effort/services/unit_converter.rb +284 -0
  74. data/lib/erp_work_effort/version.rb +1 -1
  75. data/lib/tasks/populate_model_data.rake +19 -0
  76. data/lib/tasks/resume_parser.rake +18 -0
  77. metadata +80 -16
  78. data/app/controllers/erp_work_effort/erp_app/organizer/tasks/base_controller.rb +0 -13
  79. data/app/controllers/erp_work_effort/erp_app/organizer/tasks/work_efforts_controller.rb +0 -128
  80. data/db/data_migrations/20131213151309_create_tasks_organizer_application.rb +0 -14
  81. data/db/migrate/20130829153419_base_routes.rb +0 -93
  82. data/db/migrate/20131213140617_add_work_effort_role_assignments.rb +0 -16
  83. data/db/migrate/20131213144223_add_work_item_to_work_effort.rb +0 -8
  84. data/db/migrate/20140106184615_update_work_effort_item_id_to_int.rb +0 -27
@@ -0,0 +1,21 @@
1
+ # create_table :timesheet_party_roles do |t|
2
+ # t.references :timesheet
3
+ # t.references :party
4
+ # t.referneces :role_type
5
+ #
6
+ # t.timestamps
7
+ # end
8
+ #
9
+ # add_index :timesheet_party_roles, :timesheet_id
10
+ # add_index :timesheet_party_roles, :party_id
11
+ # add_index :timesheet_party_roles, :role_type_id
12
+
13
+ class TimesheetPartyRole < ActiveRecord::Base
14
+
15
+ attr_protected :created_at, :updated_at
16
+
17
+ belongs_to :timesheet
18
+ belongs_to :party
19
+ belongs_to :role_type
20
+
21
+ end
@@ -1,11 +1,29 @@
1
+ # create_table :transportation_routes do |t|
2
+ #
3
+ # t.string :internal_identifier
4
+ # t.string :description
5
+ # t.string :comments
6
+ #
7
+ # t.string :external_identifier
8
+ # t.string :external_id_source
9
+ #
10
+ # t.timestamps
11
+ # end
12
+
1
13
  class TransportationRoute < ActiveRecord::Base
2
14
  attr_protected :created_at, :updated_at
3
15
 
16
+ tracks_created_by_updated_by
17
+ has_party_roles
18
+ has_tracked_status
19
+
4
20
  #declare array to related models
5
21
  attr_accessor :associated_records_array
6
22
 
7
23
  # This class instance variable is needed to hold the models linked within :associated_transportation_routes
8
- class << self; attr_accessor :associated_models end
24
+ class << self;
25
+ attr_accessor :associated_models
26
+ end
9
27
  @associated_models = []
10
28
 
11
29
  # Needed for polymorophic relationship with other models
@@ -27,12 +45,74 @@ class TransportationRoute < ActiveRecord::Base
27
45
  values_hash["#{record.class.name.underscore}_id"] = record.id
28
46
  values_hash["associated_record_type"] = klass_name
29
47
  values_hash["associated_record_id"] = reln_record.id
30
-
48
+
31
49
  AssociatedTransportationRoute.create(values_hash)
32
50
  end
33
51
  end
34
52
  end
35
53
 
54
+ class << self
55
+ def open_entries
56
+ joins(:segments)
57
+ .where(transportation_routes: {manual_entry: false})
58
+ .where(transportation_route_segments: {actual_arrival: nil})
59
+ end
60
+
61
+ #
62
+ # Scoping
63
+ #
64
+
65
+ #
66
+ # scoping helpers
67
+ #
68
+
69
+ # scope by dba organization
70
+ #
71
+ # @param dba_organization [Party] dba organization to scope by
72
+ #
73
+ # @return [ActiveRecord::Relation]
74
+ def scope_by_dba_organization(dba_organization)
75
+ scope_by_party(dba_organization, {role_types: ['dba_org']})
76
+ end
77
+
78
+ alias scope_by_dba scope_by_dba_organization
79
+
80
+ # scope by work efforts assigned to the passed user
81
+ #
82
+ # @param user [User] user to look for assignments
83
+ # @param options [Hash] options to apply to this scope
84
+ # @option options [Array] :role_types role types to include in the scope
85
+ #
86
+ # @return [ActiveRecord::Relation]
87
+ def scope_by_user(user, options={})
88
+ scope_by_party(user.party, options)
89
+ end
90
+
91
+ # scope by party
92
+ #
93
+ # @param party [Integer | Party | Array] either a id of Party record, a Party record, an array of Party records
94
+ # or an array of Party ids
95
+ # @param options [Hash] options to apply to this scope
96
+ # @option options [Array] :role_types role types to include in the scope
97
+ #
98
+ # @return [ActiveRecord::Relation]
99
+ def scope_by_party(party, options={})
100
+ table_alias = String.random
101
+
102
+ if options[:role_types]
103
+ joins("inner join entity_party_roles as #{table_alias} on #{table_alias}.entity_record_type = 'TransportationRoute'
104
+ and #{table_alias}.entity_record_id = transportation_routes.id and
105
+ #{table_alias}.role_type_id in (#{RoleType.find_child_role_types(options[:role_types]).collect(&:id).join(',')})
106
+ and #{table_alias}.party_id in (#{Party.select('id').where(id: party).to_sql})")
107
+
108
+ else
109
+ joins("inner join entity_party_roles as #{table_alias} on #{table_alias}.entity_record_type = 'TransportationRoute'
110
+ and #{table_alias}.entity_record_id = transportation_routes.id
111
+ and #{table_alias}.party_id in (#{Party.select('id').where(id: party).to_sql})")
112
+ end
113
+ end
114
+ end
115
+
36
116
  # Gets all associated records (of any class) tied to this route
37
117
  def associated_records
38
118
  #used the declared instance variable array
@@ -41,31 +121,36 @@ class TransportationRoute < ActiveRecord::Base
41
121
  self.class.associated_models.each do |model|
42
122
  records = records | self.send(model.to_s)
43
123
  end
44
-
124
+
45
125
  #set it back to the instance variable
46
126
  self.send("associated_records_array=", records)
47
-
127
+
48
128
  records
49
129
  end
50
130
 
51
131
  # Ties a segment's from/to stops to its route, and then forces a reload of the route's stops array from its cached value
52
132
  def modify_stops(segment)
53
- stops = []
54
- stops << segment.from_stop << segment.to_stop
55
-
56
- stops.each do |stop|
57
- unless stop.nil? or stop.route == self
58
- stop.route = self
59
- stop.save
60
- end
61
- end
62
-
63
- # Force reload of the stops array since it has changed
64
- self.stops(true)
133
+ stops = []
134
+ stops << segment.from_stop << segment.to_stop
135
+
136
+ stops.each do |stop|
137
+ unless stop.nil? or stop.route == self
138
+ stop.route = self
139
+ stop.save
140
+ end
141
+ end
142
+
143
+ # Force reload of the stops array since it has changed
144
+ self.stops(true)
65
145
  end
66
146
 
67
- def test
68
- puts self.name
147
+ def to_data_hash
148
+ data = to_hash(only: [:id, :internal_identifier, :description,
149
+ :comments, :created_at, :updated_at])
150
+
151
+ data[:transportation_route_segments] = segments.collect { |item| item.to_data_hash }
152
+
153
+ data
69
154
  end
70
155
 
71
156
  end
@@ -1,9 +1,53 @@
1
+ # create_table :transportation_route_segments do |t|
2
+ #
3
+ # t.string :internal_identifier
4
+ # t.string :description
5
+ # t.string :comments
6
+ #
7
+ # t.string :external_identifier
8
+ # t.string :external_id_source
9
+ #
10
+ # t.integer :sequence
11
+ # t.datetime :estimated_start
12
+ # t.datetime :estimated_arrival
13
+ # t.datetime :actual_start
14
+ # t.datetime :actual_arrival
15
+ # t.decimal :start_mileage, :precision => 8, :scale => 1
16
+ # t.decimal :end_mileage, :precision => 8, :scale => 1
17
+ # t.integer :fuel_used
18
+ # t.decimal :miles_traveled, :precision => 8, :scale => 1
19
+ #
20
+ # t.integer :transportation_route_id
21
+ # t.integer :from_transportation_route_stop_id
22
+ # t.integer :to_transportation_route_stop_id
23
+ #
24
+ # t.timestamps
25
+ # end
26
+ #
27
+ # add_index :transportation_route_segments, :transportation_route_id, name: 'trans_route_seg_trans_route_id_idx'
28
+ # add_index :transportation_route_segments, :from_transportation_route_stop_id, name: 'trans_route_seg_from_trans_stop_idx'
29
+ # add_index :transportation_route_segments, :to_transportation_route_stop_id, name: 'trans_route_seg_to_trans_stop_idx'
30
+ #
31
+
1
32
  class TransportationRouteSegment < ActiveRecord::Base
2
33
  attr_protected :created_at, :updated_at
3
34
 
35
+ tracks_created_by_updated_by
36
+
4
37
  belongs_to :route, :class_name => "TransportationRoute", :foreign_key => "transportation_route_id"
5
38
 
6
39
  belongs_to :from_stop, :class_name => "TransportationRouteStop", :foreign_key => "from_transportation_route_stop_id"
7
40
  belongs_to :to_stop, :class_name => "TransportationRouteStop", :foreign_key => "to_transportation_route_stop_id"
8
41
 
42
+ def to_data_hash
43
+ to_hash(only: [:id, :internal_identifier, :description,
44
+ :comments,
45
+ :actual_start,
46
+ :actual_arrival,
47
+ :miles_traveled,
48
+ :created_at, :updated_at])
49
+
50
+
51
+ end
52
+
9
53
  end
@@ -1,3 +1,22 @@
1
+ # create_table :transportation_route_stops do |t|
2
+ #
3
+ # t.string :internal_identifier
4
+ # t.string :description
5
+ #
6
+ # t.integer :postal_address_id
7
+ # t.string :geoloc
8
+ # t.integer :sequence
9
+ #
10
+ # t.string :external_identifier
11
+ # t.string :external_id_source
12
+ #
13
+ # t.integer :transportation_route_id
14
+ #
15
+ # t.timestamps
16
+ # end
17
+ #
18
+ # add_index :transportation_route_stops, :transportation_route_id, name: 'trans_route_stop_trans_route_idx'
19
+
1
20
  class TransportationRouteStop < ActiveRecord::Base
2
21
  attr_protected :created_at, :updated_at
3
22
 
@@ -0,0 +1,7 @@
1
+ class WcCode < ActiveRecord::Base
2
+ attr_protected :created_at, :updated_at
3
+
4
+ tracks_created_by_updated_by
5
+
6
+ belongs_to :party
7
+ end
@@ -1,12 +1,72 @@
1
+ #### Table Definition ###########################
2
+ # create_table "work_efforts", :force => true do |t|
3
+ # t.integer "parent_id"
4
+ # t.integer "lft"
5
+ # t.integer "rgt"
6
+ # t.integer "facility_id"
7
+ # t.integer "projected_cost_money_id"
8
+ # t.integer "actual_cost_money_id"
9
+ # t.integer "fixed_asset_id"
10
+ # t.integer "work_effort_purpose_type_id"
11
+ # t.integer "work_effort_type_id"
12
+ # t.string "description"
13
+ # t.string "type"
14
+ # t.datetime "start_at"
15
+ # t.datetime "end_at"
16
+ # t.integer "work_effort_record_id"
17
+ # t.string "work_effort_record_type"
18
+ # t.integer "work_effort_item_id"
19
+ # t.string "work_effort_item_type"
20
+ # t.datetime "created_at", :null => false
21
+ # t.datetime "updated_at", :null => false
22
+ # t.text "comments"
23
+ # t.integer "percent_done"
24
+ # t.integer "duration"
25
+ # t.string "duration_unit"
26
+ # t.integer "effort"
27
+ # t.string "effort_unit"
28
+ # t.datetime "base_line_start_at"
29
+ # t.datetime "base_line_end_at"
30
+ # t.integer "base_line_percent_done"
31
+ # t.integer "project_id"
32
+ # t.text "custom_fields"
33
+ # end
34
+ #
35
+ # add_index "work_efforts", ["end_at"], :name => "index_work_efforts_on_end_at"
36
+ # add_index "work_efforts", ["fixed_asset_id"], :name => "index_work_efforts_on_fixed_asset_id"
37
+ # add_index "work_efforts", ["project_id"], :name => "work_effort_project_idx"
38
+ # add_index "work_efforts", ["work_effort_item_type", "work_effort_item_id"], :name => "work_item_idx"
39
+ # add_index "work_efforts", ["work_effort_record_id", "work_effort_record_type"], :name => "work_effort_record_id_type_idx"
40
+ #################################################
41
+
1
42
  class WorkEffort < ActiveRecord::Base
2
43
  attr_protected :created_at, :updated_at
3
44
 
45
+ cattr_accessor :task_status_complete_iid
46
+ cattr_accessor :task_status_in_progress_iid
47
+ cattr_accessor :task_resource_status_complete_iid
48
+
49
+ @@task_status_complete_iid = 'task_status_complete'
50
+ @@task_status_in_progress_iid = 'task_status_in_progress'
51
+ @@task_resource_status_complete_iid = 'task_resource_status_complete'
52
+
4
53
  acts_as_nested_set
5
54
  include ErpTechSvcs::Utils::DefaultNestedSetMethods
6
55
  has_tracked_status
7
56
 
57
+ ## How is this Work Effort related to business parties, requestors, workers, approvers
58
+ has_party_roles
59
+
60
+ tracks_created_by_updated_by
61
+
62
+ after_save :roll_up
63
+ before_move :update_parent_status_before_move!
64
+ after_move :update_parent_status!
65
+ after_destroy :update_parent_status!
66
+
8
67
  belongs_to :work_effort_item, :polymorphic => true
9
68
 
69
+ belongs_to :project
10
70
  belongs_to :work_effort_type
11
71
  belongs_to :work_effort_purpose_type
12
72
 
@@ -18,9 +78,13 @@ class WorkEffort < ActiveRecord::Base
18
78
  has_and_belongs_to_many :role_types
19
79
  alias :role_type_assignments :role_types
20
80
 
21
- ## How is this Work Effort related to business parties, requestors, workers, approvers
81
+ ## How is this Work Effort is assigned
22
82
  has_many :work_effort_party_assignments, :dependent => :destroy
23
- has_many :parties, :through => :work_effort_party_assignments
83
+ has_many :parties, :through => :work_effort_party_assignments do
84
+ def work_resources
85
+ where('work_effort_party_assignments.role_type_id' => RoleType.iid('work_resource'))
86
+ end
87
+ end
24
88
 
25
89
  ## What Inventory Items are used in the execution of this Work Effort
26
90
  has_many :work_effort_inventory_assignments, :dependent => :destroy
@@ -30,6 +94,10 @@ class WorkEffort < ActiveRecord::Base
30
94
  has_many :work_effort_fixed_asset_assignments, :dependent => :destroy
31
95
  has_many :fixed_assets, :through => :work_effort_fixed_asset_assignments
32
96
 
97
+ ## What BizTxnEvents have been related to this WorkEffort
98
+ has_many :work_effort_biz_txn_events, :dependent => :destroy
99
+ has_many :biz_txn_events, :through => :work_effort_biz_txn_events
100
+
33
101
  ## Allow for polymorphic subtypes of this class
34
102
  belongs_to :work_effort_record, :polymorphic => true
35
103
 
@@ -37,63 +105,247 @@ class WorkEffort < ActiveRecord::Base
37
105
  belongs_to :actual_cost, :class_name => 'Money', :foreign_key => 'actual_cost_money_id'
38
106
  belongs_to :facility
39
107
 
108
+ has_many :time_entries
109
+ has_many :associated_transportation_routes, as: :associated_record
110
+ has_many :transportation_routes, through: :associated_transportation_routes
111
+
40
112
  class << self
41
- def work_efforts_for_party(party, status=nil)
42
- role_types_tbl = RoleType.arel_table
43
- parties_tbl = Party.arel_table
44
113
 
45
- statement = self
114
+ # Filter records
115
+ #
116
+ # @param filters [Hash] a hash of filters to be applied,
117
+ # @param statement [ActiveRecord::Relation] the query being built
118
+ # @return [ActiveRecord::Relation] the query being built
119
+ def apply_filters(filters, statement)
120
+ work_efforts_tbl = WorkEffort.arel_table
121
+
122
+ # filter by description
123
+ unless filters[:description].blank?
124
+ statement = statement.where(work_efforts_tbl[:description].matches("%#{filters[:description]}%"))
125
+ end
126
+
127
+ # filter by WorkEffortType
128
+ unless filters[:work_effort_type_iids].blank?
129
+ statement = statement.where(work_effort_type_id: WorkEffortType.where(internal_identifier: filters[:work_effort_type_iids]))
130
+ end
131
+
132
+ # filter by Status
133
+ unless filters[:status].blank?
134
+ statement = statement.with_current_status(filters[:status].split(','))
135
+ end
136
+
137
+ # filter by start_at
138
+ unless filters[:start_date].blank?
139
+ statement = statement.where(work_efforts_tbl[:start_at].gteq(Date.parse(filters[:start_date])))
140
+ end
46
141
 
47
- # apply status if passed
48
- statement = statement.with_status(status) if status
142
+ # filter by end_at
143
+ unless filters[:end_date].blank?
144
+ statement = statement.where(work_efforts_tbl[:end_at].lteq(Date.parse(filters[:end_date])))
145
+ end
49
146
 
50
- statement.includes(:role_types)
51
- .includes(:parties)
52
- .where(role_types_tbl[:id].in(party.party_roles.collect(&:role_type_id)).or(parties_tbl[:id].eq(party.id)))
147
+ # filter by assigned to
148
+ unless filters[:assigned_to_ids].blank?
149
+ work_effort_party_assignments_tbl = WorkEffortPartyAssignment.arel_table
150
+
151
+ statement = statement.joins(:work_effort_party_assignments)
152
+ .where(work_effort_party_assignments_tbl[:role_type_id].in(RoleType.find_child_role_types([RoleType.work_resource]).collect(&:id)))
153
+ .where(work_effort_party_assignments_tbl[:party_id].in(filters[:assigned_to_ids]))
154
+ end
155
+
156
+ # filter by project
157
+ unless filters[:project_ids].blank?
158
+ statement = statement.where(work_efforts_tbl[:project_id].in(filters[:project_ids]))
159
+ end
160
+
161
+ # filter by parties
162
+ unless filters[:parties].blank?
163
+ data = JSON.parse(filters[:parties])
164
+
165
+ statement = statement.scope_by_party(data['party_ids'].split(','),
166
+ {role_types: RoleType.where('internal_identifier' => data['role_types'].split(','))})
167
+ end
168
+
169
+ statement
53
170
  end
54
- end
55
171
 
56
- def <=>(an_other)
57
- case an_other.current_status
58
- when 'pending'
59
- 1
60
- when 'complete'
61
- 2
172
+ #
173
+ # scoping helpers
174
+ #
175
+
176
+ # scope by dba organization
177
+ #
178
+ # @param dba_organization [Party] dba organization to scope by
179
+ #
180
+ # @return [ActiveRecord::Relation]
181
+ def scope_by_dba_organization(dba_organization)
182
+ scope_by_party(dba_organization, {role_types: [RoleType.iid('dba_org')]})
183
+ end
184
+
185
+ alias scope_by_dba scope_by_dba_organization
186
+
187
+ # scope by project
188
+ #
189
+ # @param project [Integer | Project | Array] either a id of Project record, a Project record, an array of Project records
190
+ # or an array of Project ids
191
+ #
192
+ # @return [ActiveRecord::Relation]
193
+ def scope_by_project(project)
194
+ where(project_id: project)
195
+ end
196
+
197
+ # scope by party
198
+ #
199
+ # @param party [Integer | Party | Array] either a id of Party record, a Party record, an array of Party records
200
+ # or an array of Party ids
201
+ # @param options [Hash] options to apply to this scope
202
+ # @option options [Array] :role_types role types to include in the scope
203
+ #
204
+ # @return [ActiveRecord::Relation]
205
+ def scope_by_party(party, options={})
206
+ table_alias = String.random
207
+
208
+ if options[:role_types]
209
+ joins("inner join entity_party_roles as #{table_alias} on #{table_alias}.entity_record_type = 'WorkEffort'
210
+ and #{table_alias}.entity_record_id = work_efforts.id and
211
+ #{table_alias}.role_type_id in (#{RoleType.find_child_role_types(options[:role_types]).collect(&:id).join(',')})
212
+ and #{table_alias}.party_id in (#{Party.select('id').where(id: party).to_sql})")
213
+
62
214
  else
63
- 3
215
+ joins("inner join entity_party_roles as #{table_alias} on #{table_alias}.entity_record_type = 'WorkEffort'
216
+ and #{table_alias}.entity_record_id = work_efforts.id
217
+ and #{table_alias}.party_id in (#{Party.select('id').where(id: party).to_sql})")
218
+ end
64
219
  end
220
+
221
+ # scope by work efforts assigned to the passed user
222
+ #
223
+ # @param user [User] user to look for assignments
224
+ # @param options [Hash] options to apply to this scope
225
+ # @option options [Array] :role_types role types to include in the scope
226
+ #
227
+ # @return [ActiveRecord::Relation]
228
+ def scope_by_user(user, options={})
229
+ scope_by_party_assignment(user.party, options)
230
+ end
231
+
232
+ # scope by work efforts assigned to the passed party
233
+ #
234
+ # @param party [Party] party to look for assignments
235
+ # @param options [Hash] options to apply to this scope
236
+ # @option options [Array] :role_types role types to include in the scope
237
+ #
238
+ # @return [ActiveRecord::Relation]
239
+ def scope_by_party_assignment(party, options={})
240
+ statement = joins("join work_effort_party_assignments wepa on wepa.work_effort_id = work_efforts.id and wepa.party_id = #{party.id}")
241
+
242
+ if options[:role_types]
243
+ statement = statement.where("wepa.role_type_id" => RoleType.find_child_role_types(options[:role_types]))
244
+ end
245
+
246
+ statement
247
+ end
248
+ end
249
+
250
+ def to_s
251
+ self.description
252
+ end
253
+
254
+ def to_label
255
+ self.description
256
+ end
257
+
258
+ def dba_organization
259
+ find_party_with_role(RoleType.dba_org)
260
+ end
261
+
262
+ # Get assigned parties by role type
263
+ #
264
+ # @param role_types [Array] (['work_resource']) Array of role types to scope by
265
+ # @return [Array] Assigned Parties
266
+ def assigned_parties(role_types=['work_resource'])
267
+ role_types = RoleType.find_child_role_types(role_types)
268
+
269
+ Party.joins(work_effort_party_assignments: :role_type)
270
+ .where(role_types: {id: role_types})
271
+ .where(work_effort_party_assignments: {work_effort_id: self.id})
272
+ end
273
+
274
+ # Returns true if the party is assigned to WorkEffort
275
+ #
276
+ # @param party [Party] Party to check if it is assigned
277
+ # @param role_types [Array] Array of role types to check the assignments for
278
+ def party_assigned?(party, role_types=['work_resource'])
279
+ !WorkEffort.joins(work_effort_party_assignments: :role_type)
280
+ .where(role_types: {id: RoleType.find_child_role_types(role_types)})
281
+ .where(work_effort_party_assignments: {work_effort_id: self.id})
282
+ .where(work_effort_party_assignments: {party_id: party.id}).first.nil?
65
283
  end
66
284
 
67
- def assigned_parties(role_type='worker')
68
- self.work_effort_party_assignments.where('role_type_id = ?', RoleType.iid(role_type)).collect do |item|
285
+ # Get comma sepeated description of all Parties assigned
286
+ #
287
+ # @param role_types [Array] (['work_resource']) Array of role types to scope by
288
+ # @return [Array] descriptions of Parties comma separated
289
+ def description_of_assigned_parties(role_types=['work_resource'])
290
+ assigned_parties(role_types).collect do |item|
69
291
  item.party.description
70
292
  end.join(',')
71
293
  end
72
294
 
295
+ # get all the assigned roles
296
+ #
297
+ # @return [Array] descriptions of role types comma separated
73
298
  def assigned_roles
74
299
  self.role_types.collect(&:description).join(',')
75
300
  end
76
301
 
302
+ # get the current status of this work_effort
303
+ #
304
+ # @return [String] status
77
305
  def status
78
- # get status via has_tracked_status
79
306
  current_status
80
307
  end
81
308
 
82
309
  # return true if this effort has been started, false otherwise
310
+ #
311
+ # @return [Boolean] true if started
83
312
  def started?
84
313
  current_status.nil? ? false : true
85
314
  end
86
315
 
87
316
  # return true if this effort has been completed, false otherwise
317
+ #
318
+ # @return [Boolean] true if completed
88
319
  def completed?
89
320
  finished_at.nil? ? false : true
90
321
  end
91
322
 
323
+ # return true if this effort has been completed, false otherwise
324
+ #
325
+ # @return [Boolean]
92
326
  def finished?
93
327
  completed?
94
328
  end
95
329
 
96
- #start work effort with initial_status (string)
330
+ # Check if a party is allowed to enter time aganist this Work Effort
331
+ #
332
+ # @param party [Party] Party to test aganist
333
+ # @return [Boolean] If time entries are allowed
334
+ def time_entries_allowed?(party)
335
+ self.party_assigned?(party) and self.current_status != @@task_status_complete_iid
336
+ end
337
+
338
+ def has_time_entries?
339
+ self.time_entries.count != 0
340
+ end
341
+
342
+ def has_assigned_parties?
343
+ self.work_effort_party_assignments.count != 0
344
+ end
345
+
346
+ # start work effort with initial_status (string)
347
+ #
348
+ # @param initial_status [String] status to start at
97
349
  def start(initial_status='')
98
350
  effort = self
99
351
  unless self.descendants.flatten!.nil?
@@ -103,25 +355,255 @@ class WorkEffort < ActiveRecord::Base
103
355
 
104
356
  if current_status.nil?
105
357
  effort.current_status = initial_status
106
- effort.started_at = DateTime.now
358
+ effort.start_at = DateTime.now
107
359
  effort.save
108
360
  else
109
361
  raise 'Effort Already Started'
110
362
  end
111
363
  end
112
364
 
113
- def finish
114
- complete
365
+ # set current status of entity.
366
+ #
367
+ # This is overriding the default method to update the task assignments as well if the status is set to
368
+ # complete
369
+ #
370
+ # @param args [String, TrackedStatusType, Array] This can be a string of the internal identifier of the
371
+ # TrackedStatusType to set, a TrackedStatusType instance, or three params the status, options and party_id
372
+ def current_status=(args)
373
+ super(args)
374
+
375
+ if args.is_a?(Array)
376
+ status = args[0]
377
+ else
378
+ status = args
379
+ end
380
+
381
+ if status.is_a? TrackedStatusType
382
+ status = status.internal_identifier
383
+ end
384
+
385
+ if status == @@task_status_complete_iid
386
+ complete!
387
+ else
388
+ update_parent_status!
389
+ end
390
+ end
391
+
392
+ # Check if all this WorkEfforts descendants are complete.
393
+ # An optional ignored_id can be passed for a node that you don't want to check if
394
+ # it is complete
395
+ #
396
+ # @param ingored_id [Integer] Id of WorkEffort to ingnore it's status
397
+ def descendants_complete?(ignored_id=nil)
398
+ all_complete = true
399
+
400
+ descendants.each do |node|
401
+ unless node.id == ignored_id
402
+ if !node.is_complete?
403
+ all_complete = false
404
+ break
405
+ end
406
+ end
407
+ end
408
+
409
+ all_complete
115
410
  end
116
411
 
117
- def complete
118
- self.finished_at = Time.now
119
- self.actual_completion_time = time_diff_in_minutes(self.finished_at.to_time, self.started_at.to_time)
120
- self.save
412
+ def is_complete?
413
+ (current_status == @@task_status_complete_iid)
414
+ end
415
+
416
+ # get total hours for this WorkEffort by TimeEntries
417
+ #
418
+ def total_hours_in_seconds
419
+ if leaf?
420
+ time_entries.sum(:regular_hours_in_seconds)
421
+ else
422
+ descendants.collect(&:total_hours_in_seconds)
423
+ end
424
+ end
425
+
426
+ # get total hours for this WorkEffort by TimeEntries
427
+ def total_hours
428
+ if self.leaf?
429
+ time_entries.all.sum { |time_entry| time_entry.hours }
430
+ else
431
+ self.descendants.collect(&:total_hours)
432
+ end
433
+ end
434
+
435
+ # Calculate totals for children
436
+ #
437
+ def calculate_children_totals
438
+ self.start_at = self.descendants.order('start_at asc').first.start_at
439
+ self.end_at = self.descendants.order('end_at desc').last.end_at
440
+
441
+ lowest_duration_unit = nil
442
+ duration_total = nil
443
+ percent_done_total = 0.0
444
+ self.descendants.collect do |child|
445
+ if child.leaf?
446
+ if child.duration and child.duration > 0
447
+ duration_total = 0.0 if duration_total.nil?
448
+
449
+ duration_in_hours = ErpWorkEffort::Services::UnitConverter.convert_unit(child.duration.to_f, child.duration_unit.to_sym, :h)
450
+
451
+ percent_done_total += (duration_in_hours.to_f * (child.percent_done.to_f / 100))
452
+
453
+ if lowest_duration_unit.nil? || ErpWorkEffort::Services::UnitConverter.new(lowest_duration_unit) > child.duration_unit.to_sym
454
+ lowest_duration_unit = child.duration_unit.to_sym
455
+ end
456
+
457
+ duration_total += duration_in_hours
458
+ end
459
+ end
460
+ end
461
+
462
+ if duration_total
463
+ self.duration_unit = lowest_duration_unit.to_s
464
+ if lowest_duration_unit != :h
465
+ self.duration = ErpWorkEffort::Services::UnitConverter.convert_unit(duration_total.to_f, :h, lowest_duration_unit)
466
+ else
467
+ self.duration = duration_total
468
+ end
469
+
470
+ self.percent_done = (((percent_done_total / duration_total.to_f).round(2)) * 100)
471
+ end
472
+
473
+ lowest_effort_unit = nil
474
+ effort_total = nil
475
+ self.descendants.collect do |child|
476
+ if child.leaf?
477
+ if child.effort and child.effort > 0
478
+ effort_total = 0.0 if effort_total.nil?
479
+
480
+ if lowest_effort_unit.nil? || ErpWorkEffort::Services::UnitConverter.new(lowest_effort_unit) > child.effort_unit.to_sym
481
+ lowest_effort_unit = child.effort_unit.to_sym
482
+ end
483
+
484
+ effort_total += ErpWorkEffort::Services::UnitConverter.convert_unit(child.effort.to_f, child.effort_unit.to_sym, :h)
485
+ end
486
+ end
487
+ end
488
+
489
+ if effort_total
490
+ self.effort_unit = lowest_effort_unit.to_s
491
+ if lowest_effort_unit != :h
492
+ self.effort = ErpWorkEffort::Services::UnitConverter.convert_unit(effort_total.to_f, :h, lowest_effort_unit)
493
+ else
494
+ self.effort = effort_total
495
+ end
496
+ end
497
+
498
+ self.save!
499
+ end
500
+
501
+ # Roll up totals to parents
502
+ #
503
+ def roll_up
504
+ if self.parent
505
+ self.parent.calculate_children_totals
506
+ self.parent.roll_up
507
+ end
508
+ end
509
+
510
+ # converts this record a hash data representation
511
+ #
512
+ # @return [Hash] data of record
513
+ def to_data_hash
514
+ data = to_hash(only: [
515
+ :id,
516
+ {leaf?: :leaf},
517
+ :parent_id,
518
+ :description,
519
+ :start_at,
520
+ :end_at,
521
+ :percent_done,
522
+ :duration,
523
+ :duration_unit,
524
+ :effort,
525
+ :effort_unit,
526
+ :comments,
527
+ :sequence,
528
+ :created_at,
529
+ :updated_at,
530
+ :current_status
531
+ ]
532
+ )
533
+
534
+ data[:status] = self.try(:current_status_application).try(:to_data_hash)
535
+ data[:work_effort_type] = self.try(:work_effort_type).try(:to_data_hash)
536
+
537
+ data
121
538
  end
122
539
 
123
540
  protected
541
+
542
+ # determine difference in minutes between two times
543
+ #
544
+ # @param time_one [Time] first time
545
+ # @param time_two [Time] second time
546
+ # @return [Integer] time difference in minutes
124
547
  def time_diff_in_minutes (time_one, time_two)
125
548
  (((time_one - time_two).round) / 60)
126
549
  end
550
+
551
+ private
552
+
553
+ # completes work effort by setting finished at to Time.now and calculates
554
+ # actual_completion_time in minutes
555
+ #
556
+ def complete!
557
+ self.end_at = Time.now
558
+ self.save
559
+
560
+ self.work_effort_party_assignments.each do |assignment|
561
+ assignment.current_status = @@task_resource_status_complete_iid
562
+ end
563
+
564
+ # close all open time entries
565
+ time_entries.open_entries.each do |time_entry|
566
+ time_entry.thru_datetime = Time.now
567
+
568
+ time_entry.calculate_regular_hours_in_seconds!
569
+
570
+ time_entry.update_task_assignment_status(@@task_resource_status_complete_iid)
571
+ end
572
+
573
+ update_parent_status!
574
+ end
575
+
576
+ # Update the parent statues based on it's child nodes
577
+ #
578
+ def update_parent_status!
579
+ if parent
580
+ _parent = parent
581
+ while _parent
582
+ if _parent.descendants_complete?
583
+ _parent.current_status = @@task_status_complete_iid
584
+ elsif _parent.current_status == @@task_status_complete_iid
585
+ _parent.current_status = @@task_status_in_progress_iid
586
+ end
587
+ _parent = _parent.parent
588
+ end
589
+ end
590
+ end
591
+
592
+ # Update the parent statues based on it's child nodes before a node is moved
593
+ # ingnoring the node that is being moved
594
+ #
595
+ def update_parent_status_before_move!
596
+ if parent
597
+ _parent = parent
598
+ while _parent
599
+ if _parent.descendants_complete?(self.id)
600
+ _parent.current_status = @@task_status_complete_iid
601
+ elsif _parent.current_status == @@task_status_complete_iid
602
+ _parent.current_status = @@task_status_in_progress_iid
603
+ end
604
+ _parent = _parent.parent
605
+ end
606
+ end
607
+ end
608
+
127
609
  end