ecom_core 1.2.31 → 1.2.32

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/concerns/ecom/core/filterable.rb +14 -0
  3. data/app/models/ecom/core/crew.rb +10 -1
  4. data/app/models/ecom/core/dimension_element.rb +22 -0
  5. data/app/models/ecom/core/job_card.rb +1 -1
  6. data/app/models/ecom/core/measurement_unit.rb +55 -0
  7. data/app/models/ecom/core/overtime_sheet.rb +15 -5
  8. data/app/models/ecom/core/plan.rb +18 -0
  9. data/app/models/ecom/core/schedule_setting.rb +8 -6
  10. data/app/models/ecom/core/takeoff.rb +10 -0
  11. data/app/models/ecom/core/takeoff_calculator.rb +115 -0
  12. data/app/models/ecom/core/task.rb +11 -0
  13. data/app/models/ecom/core/task_resource.rb +2 -0
  14. data/app/models/ecom/core/task_step.rb +85 -0
  15. data/app/models/ecom/core/task_step_dependency.rb +10 -0
  16. data/app/models/ecom/core/task_template.rb +92 -0
  17. data/app/models/ecom/core/work_order.rb +26 -0
  18. data/app/models/ecom/core/work_product.rb +1 -0
  19. data/db/migrate/20191119012027_create_ecom_core_takeoff_calculators.rb +11 -0
  20. data/db/migrate/20191119012030_create_ecom_core_task_templates.rb +7 -0
  21. data/db/migrate/20191202235434_create_ecom_core_work_products.rb +0 -1
  22. data/db/migrate/{20191225100054_create_ecom_core_crews.rb → 20191207103729_create_ecom_core_crews.rb} +3 -3
  23. data/db/migrate/20191207103730_create_ecom_core_plans.rb +12 -0
  24. data/db/migrate/20191207103731_create_ecom_core_work_orders.rb +23 -0
  25. data/db/migrate/20191207103735_create_ecom_core_tasks.rb +9 -1
  26. data/db/migrate/20200813165553_create_ecom_core_measurement_units.rb +14 -0
  27. data/db/migrate/20200814043632_create_ecom_core_takeoffs.rb +14 -0
  28. data/db/migrate/20200820123719_create_ecom_core_task_steps.rb +17 -0
  29. data/db/migrate/20200821130934_create_ecom_core_task_step_dependencies.rb +16 -0
  30. data/db/migrate/20200901134912_create_ecom_core_dimension_elements.rb +23 -0
  31. data/lib/ecom/core/version.rb +1 -1
  32. data/spec/factories/dimension_elements.rb +8 -0
  33. data/spec/factories/ecom/core/maintenance_statuses.rb +1 -1
  34. data/spec/factories/ecom/core/plans.rb +8 -0
  35. data/spec/factories/ecom/core/takeoff_calculators.rb +18 -0
  36. data/spec/factories/ecom/core/takeoffs.rb +6 -0
  37. data/spec/factories/ecom/core/task_step_dependencies.rb +6 -0
  38. data/spec/factories/ecom/core/task_steps.rb +8 -0
  39. data/spec/factories/ecom/core/task_templates.rb +6 -0
  40. data/spec/factories/ecom/core/work_orders.rb +10 -0
  41. data/spec/factories/ecom/core/work_products.rb +0 -1
  42. data/spec/factories/measurement_units.rb +10 -0
  43. metadata +42 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1e0b7beafc5d7b2e1a7385f1a01db93b9d3279c41434a3e104a76e58aa0b48ac
4
- data.tar.gz: 766cbcb8b998e4725c1afe43f0c28a2a3901d8ce2e9a84fd308c5c88623ef392
3
+ metadata.gz: 18c07bbf068fb4b629257c21df23a45979e0d1f505e4002d0477d3a37926549d
4
+ data.tar.gz: 2c4efc8c1e021bbf12a87805a78722d3d1e093e8a3e53f9a75f7246c665a888d
5
5
  SHA512:
6
- metadata.gz: 6590ea1a50bdc293aa807ed135287cb6c89ecdd83e902898801f758d1d187b5003b31c77117f7d02ab8367e13b06bac4fb0054726c9e1934e8aa4279d6a36f43
7
- data.tar.gz: ead89e3cd5136806c9e1ea45fb91c46249604aad30ab6040f902b119307f34c0a34940c491866d04ff8eab5a99b33350dcdbc9a7399a8ce73155ff086d43c802
6
+ metadata.gz: 11bbca4344928bcd49f1256c6566b135467a943458be8744e78d11af0dd1626187a68a93655ee3d875a9938a4f5e87000b8c58fb4a216d0a6da5ff918997059e
7
+ data.tar.gz: 587ff863a59b121e6184f2db3226b1159c3fbafb536a49d08ec82937bf858a9f307eff4ff4b7eaeb340c850100fd22c4e82e616ec667eaef9b48068abb862f9a
@@ -0,0 +1,14 @@
1
+ module Ecom
2
+ module Core
3
+ module Filterable
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ def filter(filtering_params)
8
+ results = where(nil)
9
+ results.public_send('filter_by_condition', filtering_params) if filtering_params.present?
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -10,9 +10,11 @@ module Ecom
10
10
  before_save :set_employment_date,
11
11
  if: proc { |c| c.employment_date.nil? }
12
12
 
13
+ after_save :set_employee_id
14
+
13
15
  belongs_to :crew_type
14
16
 
15
- validates :name, :address, :qualification, :employment, :wage, :wage_in_words, presence: true
17
+ validates :name, :address, :qualification, :employment, :wage, :guarantor_name, :guarantor_phone, presence: true
16
18
  validates :employment, inclusion: EMPLOYMENT_TYPES
17
19
 
18
20
  has_many :project_crews
@@ -25,6 +27,13 @@ module Ecom
25
27
  def set_employment_date
26
28
  self.employment_date = Date.today
27
29
  end
30
+
31
+ def set_employee_id
32
+ company = Ecom::Core::Company.first
33
+ date = employment_date.to_s[0..3]
34
+ company_name = company ? company.name : ''
35
+ self.employee_id = "#{company_name}/#{employment}/#{date}/#{id}"
36
+ end
28
37
  end
29
38
  end
30
39
  end
@@ -0,0 +1,22 @@
1
+ module Ecom
2
+ module Core
3
+ class DimensionElement < ApplicationRecord
4
+ LENGTH = 'Length'.freeze
5
+ WIDTH = 'Width'.freeze
6
+ HEIGHT = 'Height'.freeze
7
+ RADIUS = 'Radius'.freeze
8
+ DIAMETER = 'Diameter'.freeze
9
+ DIMENSION_ELEMENT_NAMES = %w[LENGTH WIDTH HEIGHT RADIUS DIAMETER].freeze
10
+
11
+ validates :work_product_id, :work_product, :measurement_unit_id, :measurement_unit,
12
+ :amount, :dimension_element_name, presence: true
13
+
14
+ validates :dimension_element_name, inclusion: DIMENSION_ELEMENT_NAMES
15
+
16
+ validates :amount, numericality: { greater_than: 0 }
17
+
18
+ belongs_to :work_product
19
+ belongs_to :measurement_unit
20
+ end
21
+ end
22
+ end
@@ -13,4 +13,4 @@ module Ecom
13
13
  validates :code, presence: true, uniqueness: true
14
14
  end
15
15
  end
16
- end
16
+ end
@@ -0,0 +1,55 @@
1
+ module Ecom
2
+ module Core
3
+ class MeasurementUnit < ApplicationRecord
4
+ METRIC = 'Metric'.freeze
5
+ IMPERIAL = 'Imperial'.freeze
6
+ SYSTEM_OF_MEASURMENT = [METRIC, IMPERIAL].freeze
7
+
8
+ # commonly used physical quantities in construction
9
+ LENGTH = 'Length'.freeze
10
+ MASS = 'Mass'.freeze
11
+ TIME = 'Time'.freeze
12
+ CURRENT = 'Current'.freeze
13
+ TEMPRATURE = 'Temprature'.freeze
14
+
15
+ AREA = 'Area'.freeze
16
+ VOLUME = 'Volume'.freeze
17
+ ENERGY = 'Energy'.freeze
18
+ DENSITY = 'DENSITY'.freeze
19
+
20
+ PHYSICAL_QUANTITIES = [LENGTH, MASS, TIME, CURRENT, TEMPRATURE, AREA, VOLUME, ENERGY, DENSITY].freeze
21
+
22
+ validates :name, :abbrivation, :physical_quantity,
23
+ :conversion_factor, :system_of_measurement, presence: true
24
+
25
+ validates :is_si_unit, inclusion: { in: [true, false] }
26
+
27
+ validates :system_of_measurement, inclusion: SYSTEM_OF_MEASURMENT
28
+ validates :physical_quantity, inclusion: PHYSICAL_QUANTITIES
29
+
30
+ validate :si_unit_with_conversion_factor
31
+ validate :si_unit_system_of_measurement_and_physical_quantity
32
+
33
+ def si_unit_with_conversion_factor
34
+ if is_si_unit && conversion_factor != 1
35
+ errors.add(:base, 'Conversion factor for an SI unit has to be 1')
36
+ elsif !is_si_unit && conversion_factor == 1
37
+ errors.add(:base, 'Only SI units can have a conversion factor 1')
38
+ end
39
+ end
40
+
41
+ # scope si unit with system of measurement and physical quantity
42
+ def si_unit_system_of_measurement_and_physical_quantity
43
+ return if system_of_measurement.nil? || physical_quantity.nil?
44
+
45
+ existing_record = MeasurementUnit
46
+ .where(system_of_measurement: system_of_measurement,
47
+ physical_quantity: physical_quantity, is_si_unit: true).first
48
+ return unless (new_record? && existing_record.present?) || (persisted? && existing_record != self)
49
+
50
+ errors.add(:is_si_unit,
51
+ 'There exist an si unit of measurement for the given physical quantity and system of measurement')
52
+ end
53
+ end
54
+ end
55
+ end
@@ -13,19 +13,29 @@ module Ecom
13
13
  scope :by_date, ->(date) { where(date: date) }
14
14
  scope :by_status, ->(status) { where(status: status) }
15
15
  scope :by_date_between, ->(from, to) { where('date BETWEEN ? AND ?', from, to) }
16
- scope :open, ->(id) { where(status: StatusConstants::OPEN, project_id: id) }
17
- scope :submitted, ->(id) { where(status: StatusConstants::SUBMITTED, project_id: id) }
16
+ # scope :open, ->(id) { where(status: StatusConstants::OPEN, project_id: id) }
17
+ # scope :submitted, ->(id) { where(status: StatusConstants::SUBMITTED, project_id: id) }
18
18
 
19
19
  def self.open_exists?(project_id)
20
- OvertimeSheet.open(project_id).exists?
20
+ OvertimeSheet
21
+ .by_project(project_id)
22
+ .by_status(StatusConstants::OPEN)
23
+ .exists?
21
24
  end
22
25
 
23
26
  def self.open_for_date_exists?(date, project_id)
24
- OvertimeSheet.open(project_id).where(date: date).exists?
27
+ OvertimeSheet
28
+ .by_project(project_id)
29
+ .by_status(StatusConstants::OPEN)
30
+ .by_date(date)
31
+ .exists?
25
32
  end
26
33
 
27
34
  def self.exists_for_date?(date, project_id)
28
- OvertimeSheet.by_project(project_id).by_date(date).exists?
35
+ OvertimeSheet
36
+ .by_project(project_id)
37
+ .by_date(date)
38
+ .exists?
29
39
  end
30
40
 
31
41
  # Overtime sheet should be created using the
@@ -0,0 +1,18 @@
1
+ module Ecom
2
+ module Core
3
+ class Plan < ApplicationRecord
4
+ NEW = 'New'.freeze
5
+ READY_TO_START = 'Ready to start'.freeze
6
+ IN_PROGRESS = 'In Progress'.freeze
7
+ COMPLETED = 'Completed'.freeze
8
+
9
+ STATUSES = [NEW, READY_TO_START, IN_PROGRESS, COMPLETED].freeze
10
+
11
+ validates :name, :start_date, :end_date, :status, presence: true
12
+
13
+ validates :status, presence: true, inclusion: STATUSES
14
+
15
+ has_many :tasks
16
+ end
17
+ end
18
+ end
@@ -1,9 +1,11 @@
1
- module Ecom::Core
2
- class ScheduleSetting < ApplicationRecord
3
- belongs_to :equipment
4
- belongs_to :maintenance_type
5
- belongs_to :schedule_unit
1
+ module Ecom
2
+ module Core
3
+ class ScheduleSetting < ApplicationRecord
4
+ belongs_to :equipment
5
+ belongs_to :maintenance_type
6
+ belongs_to :schedule_unit
6
7
 
7
- validates :value, presence: true
8
+ validates :value, presence: true
9
+ end
8
10
  end
9
11
  end
@@ -0,0 +1,10 @@
1
+ module Ecom
2
+ module Core
3
+ class Takeoff < ApplicationRecord
4
+ validates :task_id, :takeoff_quantity, :task, presence: true
5
+ validates :takeoff_quantity, numericality: { greater_than: 0 }
6
+
7
+ belongs_to :task
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,115 @@
1
+ module Ecom
2
+ module Core
3
+ class TakeoffCalculator < ApplicationRecord
4
+ INPUT_PARAMETERS_SCHEMA = {
5
+ 'type' => 'object',
6
+ 'required' => %w[name label input_type],
7
+ 'additionalProperties' => false,
8
+ 'properties' => {
9
+ 'name' => { 'type' => 'string' },
10
+ 'label' => { 'type' => 'string' },
11
+ 'input_type' => {
12
+ 'type' => 'string',
13
+ 'enum' => %w[string number measurement enum]
14
+ }
15
+ },
16
+ "patternProperties": {
17
+ "enum_values": {
18
+ 'type' => 'array'
19
+ },
20
+ "default_value": {
21
+ 'type' => 'object',
22
+ 'required' => %w[value],
23
+ 'properties' => {
24
+ 'value' => { 'type' => 'string' }
25
+ },
26
+ "patternProperties": {
27
+ "measurement_unit_id": { 'type' => 'integer' }
28
+ }
29
+ },
30
+ "validation": {
31
+ 'type' => 'object',
32
+ 'required' => %w[required],
33
+ 'properties' => {
34
+ 'required' => { 'type' => 'boolean' }
35
+ },
36
+ "patternProperties": {
37
+ "minimum": { 'type' => 'number' },
38
+ "maximum": { 'type' => 'number' }
39
+ }
40
+ }
41
+ }
42
+ }.freeze
43
+
44
+ REBAR_TAKEOFF_CALCULATOR = 'Rebar Takeoff Calculator'.freeze
45
+ FORMWORK_TAKEOFF_CALCULATOR = 'Formwork Takeoff Calculator'.freeze
46
+ CONCRETE_POURING_TAKEOFF_CALCULATOR =
47
+ 'Concrete Pouring Takeoff Calculator'.freeze
48
+
49
+ TAKEOFF_CALCULATOR_NAMES =
50
+ [
51
+ REBAR_TAKEOFF_CALCULATOR,
52
+ FORMWORK_TAKEOFF_CALCULATOR,
53
+ CONCRETE_POURING_TAKEOFF_CALCULATOR
54
+ ].freeze
55
+
56
+ validates :name, :input_parameters, presence: true
57
+ validates :name, inclusion: TAKEOFF_CALCULATOR_NAMES
58
+
59
+ validates :name, uniqueness: true
60
+
61
+ validate :input_parameters_schema
62
+ validate :measurement_unit_id_in_input_parameters
63
+ validate :enum_in_input_parameters
64
+
65
+ def input_parameters_schema
66
+ return if input_parameters.nil?
67
+
68
+ errors.add(:input_parameters, 'There has to be one or more input parameters') if input_parameters.count.zero?
69
+
70
+ validation_errors = JSON::Validator.fully_validate(INPUT_PARAMETERS_SCHEMA,
71
+ input_parameters, strict: true,
72
+ list: true, clear_cache: true)
73
+
74
+ errors.add(:input_parameters, validation_errors) unless validation_errors.empty?
75
+ end
76
+
77
+ def measurement_unit_id_in_input_parameters
78
+ return if input_parameters.nil?
79
+
80
+ input_parameters.each do |input_parameter|
81
+ next unless input_parameter['input_type'] == 'measurement' && !input_parameter['default_value'].nil?
82
+
83
+ if input_parameter['default_value']['measurement_unit_id'].nil?
84
+ errors.add(:base, 'measurement_unit_id is required')
85
+ next
86
+ end
87
+
88
+ measurement_unit_id = input_parameter['default_value']['measurement_unit_id']
89
+ measurement_unit = Ecom::Core::MeasurementUnit.find_by(id: measurement_unit_id)
90
+
91
+ errors.add(:base, "measurement unit with id #{measurement_unit_id} does not exist") if measurement_unit.nil?
92
+ end
93
+ end
94
+
95
+ def enum_in_input_parameters
96
+ return if input_parameters.nil?
97
+
98
+ input_parameters.each do |input_parameter|
99
+ next if input_parameter['input_type'] != 'enum'
100
+
101
+ if input_parameter['enum_values'].nil?
102
+ errors.add(:base, 'enum_values are requiered if the input_type is enum')
103
+ next
104
+ end
105
+
106
+ next unless !input_parameter['enum_values'].nil? &&
107
+ input_parameter['enum_values'].class == Array &&
108
+ input_parameter['enum_values'].count.zero?
109
+
110
+ errors.add(:base, 'Provide at least one enum value')
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -8,11 +8,18 @@ module Ecom
8
8
  belongs_to :work_product
9
9
  belongs_to :task_template
10
10
  belongs_to :work_package, optional: true
11
+ belongs_to :work_order, optional: true
12
+ belongs_to :plan, optional: true
11
13
  belongs_to :performer, class_name: 'Ecom::Core::User', optional: true
12
14
  belongs_to :approver, class_name: 'Ecom::Core::User', optional: true
13
15
  belongs_to :supervisor, class_name: 'Ecom::Core::User', optional: true
14
16
  belongs_to :quality_controller, class_name: 'Ecom::Core::User', optional: true
15
17
 
18
+ has_many :task_steps
19
+ has_many :task_resources
20
+ has_one :takeoff
21
+
22
+ validates_with DateRangeValidator
16
23
  validates :code, :name, :status, :percent_completed, presence: true
17
24
  validates_numericality_of :percent_completed,
18
25
  only_integer: true,
@@ -21,8 +28,12 @@ module Ecom
21
28
 
22
29
  scope :by_status, ->(status) { where(status: status) }
23
30
 
31
+ # State: planned -> The task has be planned for execution
32
+ # State: ready_to_start -> All resources for the task has been set and is ready to start
24
33
  aasm column: 'status' do
25
34
  state :new, initial: true
35
+ state :planned
36
+ state :ready_to_start
26
37
  state :in_progress
27
38
  state :submitted
28
39
  state :under_review
@@ -1,6 +1,8 @@
1
1
  module Ecom
2
2
  module Core
3
3
  class TaskResource < ApplicationRecord
4
+ validates :task_id, :task, :resource_type_id, :resource_type, presence: true
5
+
4
6
  belongs_to :task
5
7
  belongs_to :resource_type
6
8
 
@@ -0,0 +1,85 @@
1
+ module Ecom
2
+ module Core
3
+ class TaskStep < ApplicationRecord
4
+ include Filterable
5
+
6
+ before_save :update_task_progress
7
+
8
+ scope :filter_by_condition, ->(condition) { where(condition) }
9
+
10
+ validates :name, :total_contribution_percentage_to_task, presence: true
11
+ validates_with DateRangeValidator
12
+ validate :date_range_validator
13
+ validates_numericality_of :total_contribution_percentage_to_task,
14
+ greater_than: 0,
15
+ less_than_or_equal_to: 100
16
+
17
+ validate :task_step_percentage_validator, on: :create
18
+
19
+ validate :task_step_in_task_template_inclusion_validator
20
+
21
+ belongs_to :task
22
+ has_many :dependent_task_steps,
23
+ class_name: 'Ecom::Core::TaskStepDependency', foreign_key: 'dependent_task_step_id'
24
+ has_many :dependee_task_steps,
25
+ class_name: 'Ecom::Core::TaskStepDependency', foreign_key: 'dependee_task_step_id'
26
+
27
+ def date_range_validator
28
+ task_ = Ecom::Core::Task.find_by(id: task_id)
29
+
30
+ return if task_.nil?
31
+
32
+ if !start_date.nil? && start_date < task_.start_date
33
+ errors.add(:start_date, "The task steps start date can not be before the task's start date")
34
+ end
35
+
36
+ return unless !end_date.nil? && end_date > task_.end_date
37
+
38
+ errors.add(:end_date, "The task steps end date can not be after the task's end date")
39
+ end
40
+
41
+ def task_step_percentage_validator
42
+ return if task_id.nil?
43
+
44
+ task = Ecom::Core::Task.find_by(id: task_id)
45
+
46
+ return if task.nil?
47
+
48
+ total_contribution_of_all_task_steps = task.task_steps.sum(:total_contribution_percentage_to_task)
49
+
50
+ max_total_contribution = 100 - total_contribution_of_all_task_steps
51
+ return unless total_contribution_percentage_to_task > max_total_contribution
52
+
53
+ errors
54
+ .add(:total_contribution_of_all_task_steps,
55
+ "The maximum that this task step can contribute to the total task is #{max_total_contribution}")
56
+ end
57
+
58
+ def update_task_progress
59
+ return unless completed_changed? && completed == true
60
+
61
+ # update the task progress
62
+ task = self.task
63
+ percent_completed = task.percent_completed
64
+ task.percent_completed = (percent_completed + total_contribution_percentage_to_task).to_i
65
+ task.save
66
+ end
67
+
68
+ def task_step_in_task_template_inclusion_validator
69
+ return if task_id.nil?
70
+
71
+ task = Ecom::Core::Task.find_by(id: task_id)
72
+
73
+ return if task.nil?
74
+
75
+ task_template = task.task_template
76
+
77
+ return unless task_template.task_steps.nil?
78
+
79
+ errors.add(:base,
80
+ 'Task steps are not defined for the given task in the
81
+ task template, please first define the task steps in the task template.')
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,10 @@
1
+ module Ecom
2
+ module Core
3
+ class TaskStepDependency < ApplicationRecord
4
+ validates :dependent_task_step_id, :dependee_task_step_id, presence: true
5
+
6
+ belongs_to :dependent_task_step, class_name: 'Ecom::Core::TaskStep'
7
+ belongs_to :dependee_task_step, class_name: 'Ecom::Core::TaskStep'
8
+ end
9
+ end
10
+ end
@@ -1,16 +1,58 @@
1
+ require 'json-schema'
1
2
  module Ecom
2
3
  module Core
3
4
  class TaskTemplate < ApplicationRecord
5
+ TASK_STEP_SCHEMA = {
6
+ 'type' => 'object',
7
+ 'required' => %w[name total_contribution_percentage_to_task],
8
+ 'additionalProperties' => false,
9
+ 'properties' => {
10
+ 'name' => { 'type' => 'string' },
11
+ 'total_contribution_percentage_to_task' => {
12
+ 'type' => 'integer',
13
+ 'minimum' => 1,
14
+ 'maximum' => 100
15
+ }
16
+ }
17
+ }.freeze
18
+
19
+ # here the json schema has a bug ( including all properties as required)
20
+ # and because task_step_name is required on a condition ( only if the
21
+ # milestone is after a task step ), we can solve this with a workaround
22
+ # by specifying task_step_name in patternProperties.
23
+ MILESTONE_SCHEMA = {
24
+ 'type' => 'object',
25
+ 'required' => %w[name is_after_task_step],
26
+ 'additionalProperties' => false,
27
+ 'properties' => {
28
+ 'name' => { 'type' => 'string' },
29
+ 'is_after_task_step' => {
30
+ 'type' => 'boolean'
31
+ }
32
+ },
33
+ "patternProperties": {
34
+ "task_step_name": {
35
+ 'type' => 'string'
36
+ }
37
+ }
38
+ }.freeze
39
+
4
40
  has_ancestry
5
41
 
6
42
  belongs_to :task_template_type
7
43
  has_and_belongs_to_many :work_product_templates, join_table: 'ecom_core_task_templates_work_product_templates'
8
44
  has_and_belongs_to_many :resource_types, join_table: 'ecom_core_task_templates_resource_types'
45
+ belongs_to :takeoff_calculator, optional: true
9
46
 
10
47
  validates :name, :code, presence: true
11
48
  validates :code, uniqueness: true
12
49
  delegate(:name, to: :task_template_type, prefix: true)
13
50
 
51
+ validate :task_steps_schema_validator
52
+ validate :milestones_schema_validator
53
+ validate :validate_sum_of_task_step_contribution
54
+ validate :validate_milestone_task_step_association
55
+
14
56
  def full_name
15
57
  parent_name = parent&.name
16
58
  return name unless parent_name
@@ -29,6 +71,56 @@ module Ecom
29
71
  def equipment_types
30
72
  resource_types.where(type: 'Ecom::Core::EquipmentType')
31
73
  end
74
+
75
+ def task_steps_schema_validator
76
+ return if task_steps.nil?
77
+
78
+ validation_errors = JSON::Validator.fully_validate(TASK_STEP_SCHEMA,
79
+ task_steps, strict: true,
80
+ list: true, clear_cache: true)
81
+
82
+ errors.add(:task_steps, validation_errors) unless validation_errors.empty?
83
+ end
84
+
85
+ def milestones_schema_validator
86
+ return if milestones.nil?
87
+
88
+ validation_errors = JSON::Validator.fully_validate(MILESTONE_SCHEMA,
89
+ milestones, strict: true,
90
+ list: true, clear_cache: true)
91
+
92
+ errors.add(:milestones, validation_errors) unless validation_errors.empty?
93
+ end
94
+
95
+ def validate_sum_of_task_step_contribution
96
+ return if task_steps.nil?
97
+
98
+ filtered_task_steps = task_steps.select { |task_step| task_step['total_contribution_percentage_to_task'] }
99
+
100
+ sum_of_task_step_contribution =
101
+ filtered_task_steps.sum { |task_step| task_step['total_contribution_percentage_to_task'] }
102
+
103
+ errors.add(:task_steps, 'Sum of task step contributions has to be 100') if sum_of_task_step_contribution != 100
104
+ end
105
+
106
+ def validate_milestone_task_step_association
107
+ return if milestones.nil?
108
+
109
+ task_step_names = task_steps&.map { |task_step| task_step['name'] }
110
+
111
+ milestones_associated_with_task_step = milestones.select { |milestone| milestone['is_after_task_step'] }
112
+
113
+ milestones_associated_with_task_step.each do |milestone|
114
+ if milestone['task_step_name'].nil?
115
+ errors.add(:milestones, 'if the milestone is after a task step, please specify the task step name')
116
+ next
117
+ end
118
+
119
+ unless task_step_names.include? milestone['task_step_name']
120
+ errors.add(:milestone, 'task step for the given milestone does not exist in list of task steps')
121
+ end
122
+ end
123
+ end
32
124
  end
33
125
  end
34
126
  end
@@ -0,0 +1,26 @@
1
+ module Ecom
2
+ module Core
3
+ class WorkOrder < ApplicationRecord
4
+ before_save :add_reference_number
5
+
6
+ NEW = 'New'.freeze
7
+ IN_PROGRESS = 'In Progress'.freeze
8
+ COMPLETED = 'Completed'.freeze
9
+
10
+ STATUSES = [NEW, IN_PROGRESS, COMPLETED].freeze
11
+
12
+ validates :name, :assigned_to_id, :generated_by_id, :status,
13
+ :start_date, :end_date, presence: true
14
+
15
+ validates :status, presence: true, inclusion: STATUSES
16
+
17
+ belongs_to :assigned_to, class_name: 'Ecom::Core::Crew'
18
+ belongs_to :generated_by, class_name: 'Ecom::Core::User'
19
+ has_many :tasks
20
+
21
+ def add_reference_number
22
+ self.reference_number = "WO-#{(Time.now.to_f * 1000).to_i}"
23
+ end
24
+ end
25
+ end
26
+ end
@@ -30,6 +30,7 @@ module Ecom
30
30
  belongs_to :supervisor, class_name: 'Ecom::Core::User', optional: true
31
31
  belongs_to :quality_controller, class_name: 'Ecom::Core::User', optional: true
32
32
  has_many :tasks
33
+ has_many :dimension_elements
33
34
 
34
35
  validates :name, :design_reference_no, :status, :percent_completed, presence: true
35
36
  validates :design_reference_no, uniqueness: { scope: :project_id }
@@ -0,0 +1,11 @@
1
+ class CreateEcomCoreTakeoffCalculators < ActiveRecord::Migration[6.0]
2
+ def change
3
+ create_table :ecom_core_takeoff_calculators do |t|
4
+ t.string :name, unique: true
5
+ t.boolean :is_custom_calculator, null: false, default: false
6
+ t.jsonb :input_parameters
7
+
8
+ t.timestamps
9
+ end
10
+ end
11
+ end
@@ -8,9 +8,16 @@ class CreateEcomCoreTaskTemplates < ActiveRecord::Migration[6.0]
8
8
  null: false,
9
9
  index: { name: 'tt_on_ttt_indx' },
10
10
  foreign_key: { to_table: :ecom_core_task_template_types }
11
+ t.references :takeoff_calculator,
12
+ null: true,
13
+ index: { name: 'tt_on_tc_indx' },
14
+ foreign_key: { to_table: :ecom_core_takeoff_calculators }
11
15
  t.string :ancestry, index: true
12
16
  t.boolean :has_takeoff_fields, null:false, default: false
13
17
  t.jsonb :fields
18
+ t.jsonb :takeoff_fields
19
+ t.jsonb :task_steps
20
+ t.jsonb :milestones
14
21
 
15
22
  t.timestamps
16
23
  end
@@ -4,7 +4,6 @@ class CreateEcomCoreWorkProducts < ActiveRecord::Migration[6.0]
4
4
  t.string :name, null: false
5
5
  t.string :description
6
6
  t.string :design_reference_no, unique: true
7
- t.json :dimension
8
7
  t.references :product_group,
9
8
  null: true,
10
9
  index: { name: 'wp_on_pg_indx' },
@@ -9,12 +9,12 @@ class CreateEcomCoreCrews < ActiveRecord::Migration[6.0]
9
9
  t.string :qualification, null: false
10
10
  t.string :employment, null: false
11
11
  t.float :wage, null: false
12
- t.string :wage_in_words, null: false
12
+ t.string :wage_in_words
13
13
  t.date :employment_date
14
14
  t.boolean :sub_contracted, null: false, default: false
15
15
  t.string :photo
16
- t.string :guarantor_name
17
- t.string :guarantor_phone
16
+ t.string :guarantor_name, null: false
17
+ t.string :guarantor_phone, null: false
18
18
  t.boolean :active, null: false, default: true
19
19
  t.references :crew_type,
20
20
  null: false,
@@ -0,0 +1,12 @@
1
+ class CreateEcomCorePlans < ActiveRecord::Migration[6.0]
2
+ def change
3
+ create_table :ecom_core_plans do |t|
4
+ t.string :name, null: false
5
+ t.date :start_date, null: false
6
+ t.date :end_date, null: false
7
+ t.string :status, null: false, default: 'New'
8
+
9
+ t.timestamps
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,23 @@
1
+ class CreateEcomCoreWorkOrders < ActiveRecord::Migration[6.0]
2
+ def change
3
+ create_table :ecom_core_work_orders do |t|
4
+ t.string :name
5
+ t.string :reference_number
6
+ t.references :assigned_to,
7
+ null: false,
8
+ index: { name: 'wo_on_at_indx' },
9
+ foreign_key: { to_table: :ecom_core_crews }
10
+
11
+ t.references :generated_by,
12
+ null: false,
13
+ index: { name: 'wo_on_gb_indx' },
14
+ foreign_key: { to_table: :ecom_core_users }
15
+
16
+ t.date :start_date, null: false
17
+ t.date :end_date, null: false
18
+ t.string :status, null: false, default: :new
19
+
20
+ t.timestamps
21
+ end
22
+ end
23
+ end
@@ -12,8 +12,16 @@ class CreateEcomCoreTasks < ActiveRecord::Migration[6.0]
12
12
  t.string :remark
13
13
  t.references :work_package,
14
14
  null: true,
15
- index: { name: 'wp_on_wp_indx' },
15
+ index: { name: 'tasks_on_work_package_indx' },
16
16
  foreign_key: { to_table: :ecom_core_work_packages }
17
+ t.references :work_order,
18
+ null: true,
19
+ index: { name: 'tasks_on_wo_indx' },
20
+ foreign_key: { to_table: :ecom_core_work_orders }
21
+ t.references :plan,
22
+ null: true,
23
+ index: { name: 'tasks_on_plan_indx' },
24
+ foreign_key: { to_table: :ecom_core_plans }
17
25
  t.references :performer,
18
26
  null: true,
19
27
  index: { name: 'tasks_on_performer_indx' },
@@ -0,0 +1,14 @@
1
+ class CreateEcomCoreMeasurementUnits < ActiveRecord::Migration[6.0]
2
+ def change
3
+ create_table :ecom_core_measurement_units do |t|
4
+ t.string :name, null: false
5
+ t.string :abbrivation, null: false
6
+ t.string :physical_quantity, null: false
7
+ t.boolean :is_si_unit, null: false, default: false
8
+ t.float :conversion_factor, null: false
9
+ t.string :system_of_measurement, null: false
10
+
11
+ t.timestamps
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ class CreateEcomCoreTakeoffs < ActiveRecord::Migration[6.0]
2
+ def change
3
+ create_table :ecom_core_takeoffs do |t|
4
+ t.references :task,
5
+ null: false,
6
+ index: { name: 'takeoff_on_task_indx' },
7
+ foreign_key: { to_table: :ecom_core_tasks }
8
+
9
+ t.float :takeoff_quantity, null: false
10
+
11
+ t.timestamps
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,17 @@
1
+ class CreateEcomCoreTaskSteps < ActiveRecord::Migration[6.0]
2
+ def change
3
+ create_table :ecom_core_task_steps do |t|
4
+ t.references :task,
5
+ null: false,
6
+ index: { name: 'ts_on_task_indx' },
7
+ foreign_key: { to_table: :ecom_core_tasks }
8
+ t.string :name, null: false
9
+ t.date :start_date
10
+ t.date :end_date
11
+ t.float :total_contribution_percentage_to_task, null: false
12
+ t.boolean :completed, null: false, default: false
13
+
14
+ t.timestamps
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ class CreateEcomCoreTaskStepDependencies < ActiveRecord::Migration[6.0]
2
+ def change
3
+ create_table :ecom_core_task_step_dependencies do |t|
4
+ t.references :dependent_task_step,
5
+ null: false,
6
+ index: { name: 'tsd_on_dts_indx' },
7
+ foreign_key: { to_table: :ecom_core_task_steps }
8
+
9
+ t.references :dependee_task_step,
10
+ null: false,
11
+ index: { name: 'tsd_on_dependee_ts_indx' },
12
+ foreign_key: { to_table: :ecom_core_task_steps }
13
+ t.timestamps
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,23 @@
1
+ class CreateEcomCoreDimensionElements < ActiveRecord::Migration[6.0]
2
+ def change
3
+ create_table :ecom_core_dimension_elements do |t|
4
+
5
+ t.references :work_product,
6
+ null: false,
7
+ index: { name: 'dimension_on_wp_indx' },
8
+ foreign_key: { to_table: :ecom_core_work_products }
9
+
10
+ t.references :measurement_unit,
11
+ null: false,
12
+ index: { name: 'dimension_on_mu_indx' },
13
+ foreign_key: { to_table: :ecom_core_measurement_units }
14
+
15
+ t.string :dimension_element_name, null: false
16
+ t.float :amount
17
+
18
+ t.timestamps
19
+ end
20
+
21
+ add_index :ecom_core_dimension_elements, [:work_product_id, :dimension_element_name], unique: true, name: 'de_on_wp_and_den'
22
+ end
23
+ end
@@ -1,5 +1,5 @@
1
1
  module Ecom
2
2
  module Core
3
- VERSION = '1.2.31'.freeze
3
+ VERSION = '1.2.32'.freeze
4
4
  end
5
5
  end
@@ -0,0 +1,8 @@
1
+ FactoryBot.define do
2
+ factory :dimension_element, class: Ecom::Core::DimensionElement do
3
+ association :work_product
4
+ association :measurement_unit
5
+ amount { 5 }
6
+ dimension_element_name { Ecom::Core::DimensionElement::DIMENSION_ELEMENT_NAMES.sample }
7
+ end
8
+ end
@@ -1,5 +1,5 @@
1
1
  FactoryBot.define do
2
2
  factory :maintenance_status, class: Ecom::Core::MaintenanceStatus, parent: :lookup do
3
- type { 'Ecom::Core::MaintenanceStatus'}
3
+ type { 'Ecom::Core::MaintenanceStatus' }
4
4
  end
5
5
  end
@@ -0,0 +1,8 @@
1
+ FactoryBot.define do
2
+ factory :plan, class: Ecom::Core::Plan do
3
+ name { FFaker::Name.name }
4
+ start_date { Time.now }
5
+ end_date { (Time.now + 1.day).to_s }
6
+ status { Ecom::Core::Plan::STATUSES.sample }
7
+ end
8
+ end
@@ -0,0 +1,18 @@
1
+ FactoryBot.define do
2
+ factory :takeoff_calculator, class: Ecom::Core::TakeoffCalculator do
3
+ name { Ecom::Core::TakeoffCalculator::TAKEOFF_CALCULATOR_NAMES.sample }
4
+ is_custom_calculator { false }
5
+ input_parameters do
6
+ [
7
+ {
8
+ name: 'number_of_bar',
9
+ label: 'Number Of Bar',
10
+ input_type: 'number',
11
+ default_value: {
12
+ value: '1'
13
+ }
14
+ }
15
+ ]
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,6 @@
1
+ FactoryBot.define do
2
+ factory :takeoff, class: Ecom::Core::Takeoff do
3
+ association :task
4
+ takeoff_quantity { 33 }
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ FactoryBot.define do
2
+ factory :task_step_dependency, class: Ecom::Core::TaskStepDependency do
3
+ association :dependent_task_step, factory: :task_step
4
+ association :dependee_task_step, factory: :task_step
5
+ end
6
+ end
@@ -0,0 +1,8 @@
1
+ FactoryBot.define do
2
+ factory :task_step, class: Ecom::Core::TaskStep do
3
+ association :task
4
+ name { 'Cutting' }
5
+ total_contribution_percentage_to_task { 30 }
6
+ completed { false }
7
+ end
8
+ end
@@ -8,5 +8,11 @@ FactoryBot.define do
8
8
  association :task_template_type
9
9
  has_takeoff_fields { false }
10
10
  fields { [{ name: 'length', label: 'Length' }] }
11
+ task_steps do
12
+ [{
13
+ "name": 'Cutting',
14
+ "total_contribution_percentage_to_task": 100
15
+ }]
16
+ end
11
17
  end
12
18
  end
@@ -0,0 +1,10 @@
1
+ FactoryBot.define do
2
+ factory :work_order, class: Ecom::Core::WorkOrder do
3
+ name { FFaker::Name.unique.name }
4
+ association :assigned_to, factory: :crew
5
+ association :generated_by, factory: :user
6
+ start_date { (Time.now + 1.day).to_s }
7
+ end_date { (Time.now + 1.month).to_s }
8
+ status { Ecom::Core::WorkOrder::STATUSES.sample }
9
+ end
10
+ end
@@ -3,7 +3,6 @@ FactoryBot.define do
3
3
  name { FFaker::Name.name }
4
4
  description { FFaker::Name.name }
5
5
  design_reference_no { FFaker::Guid.guid }
6
- dimension { [{ label: 'Length', name: 'length', value: 12 }] }
7
6
  association :product_group
8
7
  association :work_product_template
9
8
  association :project
@@ -0,0 +1,10 @@
1
+ FactoryBot.define do
2
+ factory :measurement_unit, class: Ecom::Core::MeasurementUnit do
3
+ name { 'Meter' }
4
+ abbrivation { 'm' }
5
+ physical_quantity { Ecom::Core::MeasurementUnit::LENGTH }
6
+ is_si_unit { true }
7
+ conversion_factor { 1 }
8
+ system_of_measurement { Ecom::Core::MeasurementUnit::METRIC }
9
+ end
10
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ecom_core
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.31
4
+ version: 1.2.32
5
5
  platform: ruby
6
6
  authors:
7
7
  - Henock L.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-09-25 00:00:00.000000000 Z
11
+ date: 2020-09-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aasm
@@ -142,6 +142,20 @@ dependencies:
142
142
  - - "~>"
143
143
  - !ruby/object:Gem::Version
144
144
  version: 6.0.3
145
+ - !ruby/object:Gem::Dependency
146
+ name: json-schema
147
+ requirement: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ type: :runtime
153
+ prerelease: false
154
+ version_requirements: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
145
159
  - !ruby/object:Gem::Dependency
146
160
  name: factory_bot_rails
147
161
  requirement: !ruby/object:Gem::Requirement
@@ -238,6 +252,7 @@ files:
238
252
  - README.md
239
253
  - Rakefile
240
254
  - app/constants/ecom/core/status_constants.rb
255
+ - app/controllers/concerns/ecom/core/filterable.rb
241
256
  - app/controllers/concerns/ecom/core/lookupable.rb
242
257
  - app/controllers/concerns/ecom/core/resource_typeable.rb
243
258
  - app/controllers/ecom/core/access_controller.rb
@@ -269,6 +284,7 @@ files:
269
284
  - app/models/ecom/core/crew_type.rb
270
285
  - app/models/ecom/core/currency.rb
271
286
  - app/models/ecom/core/custom_payment_detail.rb
287
+ - app/models/ecom/core/dimension_element.rb
272
288
  - app/models/ecom/core/equipment.rb
273
289
  - app/models/ecom/core/equipment_category.rb
274
290
  - app/models/ecom/core/equipment_component.rb
@@ -286,6 +302,7 @@ files:
286
302
  - app/models/ecom/core/maintenance_status.rb
287
303
  - app/models/ecom/core/maintenance_type.rb
288
304
  - app/models/ecom/core/material_type.rb
305
+ - app/models/ecom/core/measurement_unit.rb
289
306
  - app/models/ecom/core/menu.rb
290
307
  - app/models/ecom/core/overtime_sheet.rb
291
308
  - app/models/ecom/core/overtime_sheet_entry.rb
@@ -293,6 +310,7 @@ files:
293
310
  - app/models/ecom/core/payment.rb
294
311
  - app/models/ecom/core/payment_detail.rb
295
312
  - app/models/ecom/core/payroll.rb
313
+ - app/models/ecom/core/plan.rb
296
314
  - app/models/ecom/core/product_group.rb
297
315
  - app/models/ecom/core/product_type.rb
298
316
  - app/models/ecom/core/project.rb
@@ -303,12 +321,17 @@ files:
303
321
  - app/models/ecom/core/schedule_unit.rb
304
322
  - app/models/ecom/core/stakeholder.rb
305
323
  - app/models/ecom/core/stakeholder_type.rb
324
+ - app/models/ecom/core/takeoff.rb
325
+ - app/models/ecom/core/takeoff_calculator.rb
306
326
  - app/models/ecom/core/task.rb
307
327
  - app/models/ecom/core/task_resource.rb
328
+ - app/models/ecom/core/task_step.rb
329
+ - app/models/ecom/core/task_step_dependency.rb
308
330
  - app/models/ecom/core/task_template.rb
309
331
  - app/models/ecom/core/task_template_type.rb
310
332
  - app/models/ecom/core/user.rb
311
333
  - app/models/ecom/core/user_role.rb
334
+ - app/models/ecom/core/work_order.rb
312
335
  - app/models/ecom/core/work_package.rb
313
336
  - app/models/ecom/core/work_product.rb
314
337
  - app/models/ecom/core/work_product_template.rb
@@ -328,6 +351,7 @@ files:
328
351
  - db/migrate/20190101112620_create_ecom_core_lookups.rb
329
352
  - db/migrate/20190101112625_create_ecom_core_overtime_types.rb
330
353
  - db/migrate/20191119010518_create_ecom_core_task_template_types.rb
354
+ - db/migrate/20191119012027_create_ecom_core_takeoff_calculators.rb
331
355
  - db/migrate/20191119012030_create_ecom_core_task_templates.rb
332
356
  - db/migrate/20191119013236_create_ecom_core_work_product_templates.rb
333
357
  - db/migrate/20191119144141_create_ecom_core_stakeholder_types.rb
@@ -344,9 +368,11 @@ files:
344
368
  - db/migrate/20191202221423_create_ecom_core_menus.rb
345
369
  - db/migrate/20191202222210_create_ecom_core_work_packages.rb
346
370
  - db/migrate/20191202235434_create_ecom_core_work_products.rb
371
+ - db/migrate/20191207103729_create_ecom_core_crews.rb
372
+ - db/migrate/20191207103730_create_ecom_core_plans.rb
373
+ - db/migrate/20191207103731_create_ecom_core_work_orders.rb
347
374
  - db/migrate/20191207103735_create_ecom_core_tasks.rb
348
375
  - db/migrate/20191210724614_create_ecom_core_task_resources.rb
349
- - db/migrate/20191225100054_create_ecom_core_crews.rb
350
376
  - db/migrate/20191225121850_create_ecom_core_project_crews.rb
351
377
  - db/migrate/20191225140433_create_ecom_core_attendance_sheets.rb
352
378
  - db/migrate/20191225162539_create_ecom_core_attendance_sheet_entries.rb
@@ -368,7 +394,12 @@ files:
368
394
  - db/migrate/20200602130247_create_ecom_core_requested_items.rb
369
395
  - db/migrate/20200602830603_create_ecom_core_bookings.rb
370
396
  - db/migrate/20200603115317_create_ecom_core_booked_items.rb
397
+ - db/migrate/20200813165553_create_ecom_core_measurement_units.rb
398
+ - db/migrate/20200814043632_create_ecom_core_takeoffs.rb
399
+ - db/migrate/20200820123719_create_ecom_core_task_steps.rb
400
+ - db/migrate/20200821130934_create_ecom_core_task_step_dependencies.rb
371
401
  - db/migrate/20200901085227_create_ecom_core_crew_contracts.rb
402
+ - db/migrate/20200901134912_create_ecom_core_dimension_elements.rb
372
403
  - db/migrate/20200919053331_create_ecom_core_maintenance_types.rb
373
404
  - db/migrate/20200919072216_create_ecom_core_schedule_settings.rb
374
405
  - db/migrate/20200919081338_create_ecom_core_maintenance_service_orders.rb
@@ -381,6 +412,7 @@ files:
381
412
  - lib/ecom/core/version.rb
382
413
  - lib/ecom_core.rb
383
414
  - lib/tasks/ecom_core_tasks.rake
415
+ - spec/factories/dimension_elements.rb
384
416
  - spec/factories/ecom/core/application_modules.rb
385
417
  - spec/factories/ecom/core/attendance_sheet_entries.rb
386
418
  - spec/factories/ecom/core/attendance_sheets.rb
@@ -421,6 +453,7 @@ files:
421
453
  - spec/factories/ecom/core/payment_details.rb
422
454
  - spec/factories/ecom/core/payments.rb
423
455
  - spec/factories/ecom/core/payrolls.rb
456
+ - spec/factories/ecom/core/plans.rb
424
457
  - spec/factories/ecom/core/product_groups.rb
425
458
  - spec/factories/ecom/core/product_types.rb
426
459
  - spec/factories/ecom/core/project_crews.rb
@@ -431,15 +464,21 @@ files:
431
464
  - spec/factories/ecom/core/schedule_units.rb
432
465
  - spec/factories/ecom/core/stakeholder_types.rb
433
466
  - spec/factories/ecom/core/stakeholders.rb
467
+ - spec/factories/ecom/core/takeoff_calculators.rb
468
+ - spec/factories/ecom/core/takeoffs.rb
434
469
  - spec/factories/ecom/core/task_resources.rb
470
+ - spec/factories/ecom/core/task_step_dependencies.rb
471
+ - spec/factories/ecom/core/task_steps.rb
435
472
  - spec/factories/ecom/core/task_template_types.rb
436
473
  - spec/factories/ecom/core/task_templates.rb
437
474
  - spec/factories/ecom/core/tasks.rb
438
475
  - spec/factories/ecom/core/user_roles.rb
439
476
  - spec/factories/ecom/core/users.rb
477
+ - spec/factories/ecom/core/work_orders.rb
440
478
  - spec/factories/ecom/core/work_packages.rb
441
479
  - spec/factories/ecom/core/work_product_templates.rb
442
480
  - spec/factories/ecom/core/work_products.rb
481
+ - spec/factories/measurement_units.rb
443
482
  homepage: http://www.mks.com.et
444
483
  licenses:
445
484
  - MIT