ecom_core 1.2.30 → 1.2.35

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 (104) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/ecom/core/{project_crews_controller.rb → site_crews_controller.rb} +5 -4
  3. data/app/models/ecom/core/attendance_sheet.rb +19 -19
  4. data/app/models/ecom/core/available_unit_of_measurement.rb +10 -0
  5. data/app/models/ecom/core/crew.rb +14 -3
  6. data/app/models/ecom/core/crew_contract.rb +41 -1
  7. data/app/models/ecom/core/crew_contract_transaction.rb +29 -0
  8. data/app/models/ecom/core/crew_id_card.rb +36 -0
  9. data/app/models/ecom/core/crew_time.rb +11 -3
  10. data/app/models/ecom/core/dimension_element.rb +22 -0
  11. data/app/models/ecom/core/inspection_checklist.rb +6 -0
  12. data/app/models/ecom/core/job_card.rb +1 -1
  13. data/app/models/ecom/core/material.rb +6 -0
  14. data/app/models/ecom/core/material_identity.rb +21 -0
  15. data/app/models/ecom/core/material_item.rb +18 -0
  16. data/app/models/ecom/core/material_sub_type.rb +20 -0
  17. data/app/models/ecom/core/measurement_unit.rb +55 -0
  18. data/app/models/ecom/core/overtime_sheet.rb +26 -16
  19. data/app/models/ecom/core/payroll.rb +3 -3
  20. data/app/models/ecom/core/plan.rb +18 -0
  21. data/app/models/ecom/core/project.rb +0 -3
  22. data/app/models/ecom/core/project_template.rb +11 -0
  23. data/app/models/ecom/core/project_work_item_template.rb +9 -0
  24. data/app/models/ecom/core/schedule_setting.rb +8 -6
  25. data/app/models/ecom/core/{project_crew.rb → site.rb} +8 -4
  26. data/app/models/ecom/core/site_crew.rb +27 -0
  27. data/app/models/ecom/core/takeoff.rb +10 -0
  28. data/app/models/ecom/core/task.rb +32 -4
  29. data/app/models/ecom/core/task_inspection_checklist.rb +18 -0
  30. data/app/models/ecom/core/task_resource.rb +2 -0
  31. data/app/models/ecom/core/task_template.rb +22 -2
  32. data/app/models/ecom/core/task_template_inspection_checklist.rb +8 -0
  33. data/app/models/ecom/core/work_order.rb +26 -0
  34. data/app/models/ecom/core/work_package.rb +3 -0
  35. data/app/models/ecom/core/work_product.rb +3 -18
  36. data/app/models/ecom/core/work_product_template.rb +2 -2
  37. data/app/services/ecom/core/crew_contract_transaction_service.rb +89 -0
  38. data/app/services/ecom/core/site_crew_service.rb +44 -0
  39. data/app/uploaders/ecom/core/photo_uploader.rb +8 -1
  40. data/config/routes.rb +2 -2
  41. data/db/migrate/20191119012030_create_ecom_core_task_templates.rb +6 -1
  42. data/db/migrate/20191119013236_create_ecom_core_work_product_templates.rb +0 -15
  43. data/db/migrate/20191119021405_create_ecom_core_project_templates.rb +15 -0
  44. data/db/migrate/20191119034319_create_ecom_core_project_work_item_templates.rb +20 -0
  45. data/db/migrate/20191201145849_create_ecom_core_sites.rb +16 -0
  46. data/db/migrate/20191202222210_create_ecom_core_work_packages.rb +12 -0
  47. data/db/migrate/20191202235434_create_ecom_core_work_products.rb +2 -18
  48. data/db/migrate/{20191225100054_create_ecom_core_crews.rb → 20191207103729_create_ecom_core_crews.rb} +3 -3
  49. data/db/migrate/20191207103730_create_ecom_core_plans.rb +12 -0
  50. data/db/migrate/20191207103731_create_ecom_core_work_orders.rb +23 -0
  51. data/db/migrate/20191207103735_create_ecom_core_tasks.rb +23 -7
  52. data/db/migrate/20191225140433_create_ecom_core_attendance_sheets.rb +4 -4
  53. data/db/migrate/20200126081005_create_ecom_core_payrolls.rb +3 -3
  54. data/db/migrate/20200410090701_create_ecom_core_overtime_sheets.rb +4 -4
  55. data/db/migrate/20200616044231_create_ecom_core_material_sub_types.rb +14 -0
  56. data/db/migrate/20200616051902_create_ecom_core_material_identities.rb +17 -0
  57. data/db/migrate/20200618105233_create_ecom_core_material_items.rb +16 -0
  58. data/db/migrate/20200813165553_create_ecom_core_measurement_units.rb +14 -0
  59. data/db/migrate/20200813165555_create_ecom_core_available_unit_of_measurements.rb +16 -0
  60. data/db/migrate/20200814043632_create_ecom_core_takeoffs.rb +14 -0
  61. data/db/migrate/20200901085227_create_ecom_core_crew_contracts.rb +7 -1
  62. data/db/migrate/20200901134912_create_ecom_core_dimension_elements.rb +23 -0
  63. data/db/migrate/20200919085613_create_ecom_core_job_cards.rb +1 -0
  64. data/db/migrate/20201013072924_create_ecom_core_crew_contract_transactions.rb +16 -0
  65. data/db/migrate/20201013090609_create_ecom_core_site_crews.rb.rb +22 -0
  66. data/db/migrate/20201013094100_create_ecom_core_crew_id_cards.rb +16 -0
  67. data/db/migrate/20201113050953_create_ecom_core_task_inspection_checklists.rb +22 -0
  68. data/db/migrate/20201113094302_create_ecom_core_task_template_inspection_checklists.rb +17 -0
  69. data/lib/ecom/core/version.rb +1 -1
  70. data/spec/factories/ecom/core/attendance_sheet_entries.rb +1 -1
  71. data/spec/factories/ecom/core/attendance_sheets.rb +1 -1
  72. data/spec/factories/ecom/core/available_unit_of_measurements.rb +6 -0
  73. data/spec/factories/ecom/core/crew_contract_transactions.rb +8 -0
  74. data/spec/factories/ecom/core/crew_contracts.rb +12 -0
  75. data/spec/factories/ecom/core/crew_id_cards.rb +8 -0
  76. data/spec/factories/ecom/core/dimension_elements.rb +8 -0
  77. data/spec/factories/ecom/core/inspection_checklists.rb +5 -0
  78. data/spec/factories/ecom/core/maintenance_statuses.rb +1 -1
  79. data/spec/factories/ecom/core/material_identities.rb +6 -0
  80. data/spec/factories/ecom/core/material_items.rb +6 -0
  81. data/spec/factories/ecom/core/material_sub_types.rb +6 -0
  82. data/spec/factories/ecom/core/materials.rb +5 -0
  83. data/spec/factories/ecom/core/measurement_units.rb +16 -0
  84. data/spec/factories/ecom/core/overtime_sheets.rb +1 -1
  85. data/spec/factories/ecom/core/payrolls.rb +1 -1
  86. data/spec/factories/ecom/core/plans.rb +8 -0
  87. data/spec/factories/ecom/core/project_templates.rb +8 -0
  88. data/spec/factories/ecom/core/project_work_item_templates.rb +7 -0
  89. data/spec/factories/ecom/core/site_crews.rb +10 -0
  90. data/spec/factories/ecom/core/sites.rb +8 -0
  91. data/spec/factories/ecom/core/takeoffs.rb +6 -0
  92. data/spec/factories/ecom/core/task_inspection_checklists.rb +11 -0
  93. data/spec/factories/ecom/core/task_template_inspection_checklists.rb +7 -0
  94. data/spec/factories/ecom/core/task_templates.rb +11 -1
  95. data/spec/factories/ecom/core/tasks.rb +8 -3
  96. data/spec/factories/ecom/core/work_orders.rb +10 -0
  97. data/spec/factories/ecom/core/work_packages.rb +6 -0
  98. data/spec/factories/ecom/core/work_product_templates.rb +0 -1
  99. data/spec/factories/ecom/core/work_products.rb +1 -8
  100. metadata +75 -9
  101. data/app/services/ecom/core/project_crew_service.rb +0 -39
  102. data/db/migrate/20191225121850_create_ecom_core_project_crews.rb +0 -19
  103. data/db/migrate/20200922044959_add_costs_approved_to_job_card.rb +0 -5
  104. data/spec/factories/ecom/core/project_crews.rb +0 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: af96604702a7c0a83f0567e1a444bc4fa80b8714f7af4da402d2868937f3e2db
4
- data.tar.gz: dc7848bedb34f4e898f90f8f0aa5cdc1c2485172332578af2cb748ca3e14fa3e
3
+ metadata.gz: b43b8dd7f97b9f42d8291e7de02613863b54d79062ab12079792eaabfcdcc78a
4
+ data.tar.gz: 35e01c0eecc8d2ee6d11599f834cd2cb5dbfcdb18a90884bf88401fe18359b8e
5
5
  SHA512:
6
- metadata.gz: dbe2542e2087c03011bb0e370eba3da5c2c5b2e40b01e0990750f8964f8aac0510d2ae1a71bee8ec107130dc7fed3e8ec04ab4f96a99d61921f56024beeab259
7
- data.tar.gz: cd9546c5d8b5796d3f50688dbf202127f942dd838bff0cbcd3a096943efb5037df103d87256f1d6b1cab71b9e7dafcc3b41572b50d185c390990d81a789f5ffa
6
+ metadata.gz: '0428fe3fac34831c9ab94b28f4a7819dee36fd608f0d2534249b06278918e8e87313b417279a25328061500833dcf2e281fb43ef8add4b463e824bdac61c3958'
7
+ data.tar.gz: 673cf08cc11e5f3ddc8d8ddef5f3da3adb17292032b8e306deacf4bc326080a5da3ea8c666d71b8014a4b91e690a1267c4f18d920cf45133f864910ccac2d7aa
@@ -1,11 +1,12 @@
1
1
  module Ecom
2
2
  module Core
3
- class ProjectCrewsController < ApplicationController
3
+ class SiteCrewsController < ApplicationController
4
4
  before_action :set_service, only: %i[update]
5
5
 
6
6
  def index
7
- project = Ecom::Core::Project.find(params[:id])
8
- serialized = ActiveModelSerializers::SerializableResource.new(project.crews)
7
+ crew_ids = Ecom::Core::SiteCrew.where(site_id: params[:id], status: 'Active').select(:crew_id)
8
+ crews = Ecom::Core::Crew.where(id: crew_ids)
9
+ serialized = ActiveModelSerializers::SerializableResource.new(crews)
9
10
  render json: { success: true, data: serialized }
10
11
  end
11
12
 
@@ -18,7 +19,7 @@ module Ecom
18
19
  private
19
20
 
20
21
  def set_service
21
- @service = ProjectCrewService.new
22
+ @service = SiteCrewService.new
22
23
  end
23
24
  end
24
25
  end
@@ -1,40 +1,40 @@
1
1
  module Ecom
2
2
  module Core
3
3
  class AttendanceSheet < ApplicationRecord
4
- validates :date, presence: true, uniqueness: { scope: :project_id }
4
+ validates :date, presence: true, uniqueness: { scope: :site_id }
5
5
  validates :status, inclusion: StatusConstants::STATUSES
6
6
 
7
- belongs_to :project
7
+ belongs_to :site
8
8
 
9
9
  has_many :attendance_sheet_entries
10
10
  has_many :crew_times, through: :attendance_sheet_entries
11
11
 
12
- scope :by_project, ->(id) { where(project_id: id) }
12
+ scope :by_site, ->(id) { where(site_id: id) }
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
16
 
17
- def self.open_for_date(date, project_id)
18
- AttendanceSheet.find_by(status: StatusConstants::OPEN, date: date, project_id: project_id)
17
+ def self.open_for_date(date, site_id)
18
+ AttendanceSheet.find_by(status: StatusConstants::OPEN, date: date, site_id: site_id)
19
19
  end
20
20
 
21
- def self.open_exists?(project_id)
21
+ def self.open_exists?(site_id)
22
22
  AttendanceSheet
23
- .by_project(project_id)
23
+ .by_site(site_id)
24
24
  .by_status(StatusConstants::OPEN)
25
25
  .exists?
26
26
  end
27
27
 
28
- def self.exists_for_today?(project_id)
28
+ def self.exists_for_today?(site_id)
29
29
  AttendanceSheet
30
- .by_project(project_id)
30
+ .by_site(site_id)
31
31
  .by_date(Date.today)
32
32
  .exists?
33
33
  end
34
34
 
35
- def self.open_exists_for_today?(project_id)
35
+ def self.open_exists_for_today?(site_id)
36
36
  AttendanceSheet
37
- .by_project(project_id)
37
+ .by_site(site_id)
38
38
  .by_date(Date.today)
39
39
  .by_status(StatusConstants::OPEN)
40
40
  .exists?
@@ -45,19 +45,19 @@ module Ecom
45
45
  # check if there is an open attendance already,
46
46
  # and also that we have only one attendace sheet
47
47
  # per day.
48
- def self.create_current(project_id)
49
- raise 'Attendance sheet already created for the day.' if AttendanceSheet.exists_for_today?(project_id)
48
+ def self.create_current(site_id)
49
+ raise 'Attendance sheet already created for the day.' if AttendanceSheet.exists_for_today?(site_id)
50
50
 
51
- if AttendanceSheet.open_exists?(project_id)
51
+ if AttendanceSheet.open_exists?(site_id)
52
52
  raise 'There is an open attendance sheet which needs to be submitted before creating a new one.'
53
53
  end
54
54
 
55
55
  AttendanceSheet.create(date: Date.today, opened_at: Time.now, status: StatusConstants::OPEN,
56
- project_id: project_id)
56
+ site_id: site_id)
57
57
  end
58
58
 
59
- def self.submit_current(project_id)
60
- sheet = AttendanceSheet.find_by(date: Date.today, status: StatusConstants::OPEN, project_id: project_id)
59
+ def self.submit_current(site_id)
60
+ sheet = AttendanceSheet.find_by(date: Date.today, status: StatusConstants::OPEN, site_id: site_id)
61
61
 
62
62
  raise 'There is no open attendance sheet to submit.' if sheet.nil?
63
63
 
@@ -71,8 +71,8 @@ module Ecom
71
71
  # to submit the attendance sheet after the date has
72
72
  # passed. Normally, timekeepers need to open and close
73
73
  # an attendance sheet of a date on the specific date.
74
- def self.submit(date, project_id)
75
- sheet = AttendanceSheet.open_for_date(date, project_id)
74
+ def self.submit(date, site_id)
75
+ sheet = AttendanceSheet.open_for_date(date, site_id)
76
76
  raise 'There is no open attendance sheet to submit for the selected day.' unless sheet
77
77
 
78
78
  sheet.submitted_at = DateTime.now
@@ -0,0 +1,10 @@
1
+ module Ecom
2
+ module Core
3
+ class AvailableUnitOfMeasurement < ApplicationRecord
4
+ validates :material_type_id, :material_type, :measurement_unit_id, :measurement_unit, presence: true
5
+
6
+ belongs_to :material_type
7
+ belongs_to :measurement_unit
8
+ end
9
+ end
10
+ end
@@ -10,13 +10,16 @@ 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
- has_many :project_crews
19
- has_many :projects, through: :project_crews
20
+ has_many :site_crews
21
+ has_many :sites, through: :site_crews
22
+ has_many :crew_id_cards
20
23
 
21
24
  scope :by_active, ->(active) { where(active: active) }
22
25
  scope :by_qualification, ->(qualification) { where(qualification: qualification) }
@@ -25,6 +28,14 @@ module Ecom
25
28
  def set_employment_date
26
29
  self.employment_date = Date.today
27
30
  end
31
+
32
+ def set_employee_id
33
+ company = Ecom::Core::Company.first
34
+ date = employment_date.to_s[0..3]
35
+ company_name = company ? company.name : ''
36
+ employee_id = "#{company_name}/#{employment}/#{date}/#{id}"
37
+ update_column(:employee_id, employee_id)
38
+ end
28
39
  end
29
40
  end
30
41
  end
@@ -1,17 +1,57 @@
1
1
  module Ecom
2
2
  module Core
3
3
  class CrewContract < ApplicationRecord
4
+ include AASM
5
+
4
6
  belongs_to :crew
7
+ belongs_to :crew_type
5
8
 
6
- validates :contract_no, :from, :to, :place_of_work, :probation_period, :contract_type, presence: true
9
+ validates :status, :contract_no, :from, :to, :place_of_work, :probation_period,
10
+ :contract_type, :wage, :wage_in_words, :crew_type_id, :crew_type, presence: true
7
11
  validates :contract_no, uniqueness: true
8
12
  validate :validate_date_range
9
13
 
14
+ validates :wage, presence: true, numericality: { greater_than: 0 }
15
+
16
+ validates_uniqueness_of :crew_id,
17
+ conditions: -> { where(status: :in_effect) },
18
+ message: 'There can only be one contract' \
19
+ ' with status `In effect` for a given crew at a time'
20
+
10
21
  def validate_date_range
11
22
  return unless from && to
12
23
 
13
24
  errors.add(:to, 'cannot be before from date.') if from >= to
14
25
  end
26
+
27
+ aasm column: 'status' do
28
+ state :draft, initial: true
29
+ state :submitted_for_approval
30
+ state :approved
31
+ state :rejected
32
+ state :in_effect
33
+ state :void
34
+
35
+ event :submit_for_approval do
36
+ transitions from: :draft, to: :submitted_for_approval, guard: -> { status == 'draft' }
37
+ end
38
+
39
+ event :approve do
40
+ transitions from: :submitted_for_approval, to: :approved, guard: -> { status == 'submitted_for_approval' }
41
+ end
42
+
43
+ event :reject do
44
+ transitions from: :submitted_for_approval, to: :rejected, guard: -> { status == 'submitted_for_approval' }
45
+ end
46
+
47
+ event :commence do
48
+ transitions from: :approved, to: :in_effect, guard: -> { status == 'approved' }
49
+ end
50
+
51
+ event :terminate do
52
+ transitions from: :in_effect, to: :void, guard: -> { status == 'in_effect' }
53
+ end
54
+ end
15
55
  end
16
56
  end
17
57
  end
@@ -0,0 +1,29 @@
1
+ module Ecom
2
+ module Core
3
+ class CrewContractTransaction < ApplicationRecord
4
+ PENDING = 'Pending'.freeze
5
+ EXECUTED = 'Executed'.freeze
6
+
7
+ STATUSES = [PENDING, EXECUTED].freeze
8
+
9
+ TXN_UPDATE_CREW_TYPE = 'TXN_UPDATE_CREW_TYPE'.freeze
10
+ TXN_UPDATE_WAGE = 'TXN_UPDATE_WAGE'.freeze
11
+ TXN_UPDATE_VALIDITY = 'TXN_UPDATE_VALIDITY'.freeze
12
+ TXN_UPDATE_PLACE_OF_WORK = 'TXN_UPDATE_PLACE_OF_WORK'.freeze
13
+ TXN_TERMINATE_CONTRACT = 'TXN_TERMINATE_CONTRACT'.freeze
14
+
15
+ TRANSACTION_TYPES = [TXN_UPDATE_CREW_TYPE,
16
+ TXN_UPDATE_WAGE, TXN_UPDATE_VALIDITY,
17
+ TXN_UPDATE_PLACE_OF_WORK,
18
+ TXN_TERMINATE_CONTRACT].freeze
19
+
20
+ validates :status, :transaction_type, :crew_contract_id,
21
+ :crew_contract, :effective_date, presence: true
22
+
23
+ validates :status, inclusion: STATUSES
24
+ validates :transaction_type, inclusion: TRANSACTION_TYPES
25
+
26
+ belongs_to :crew_contract
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,36 @@
1
+ module Ecom
2
+ module Core
3
+ class CrewIdCard < ApplicationRecord
4
+ before_save :invalidate_other_ids
5
+
6
+ VALID = 'Valid'.freeze
7
+ INVALID = 'Invalid'.freeze
8
+
9
+ STATUSES = [VALID, INVALID].freeze
10
+
11
+ validates :crew_id, :crew, :issued_on, :valid_until,
12
+ :status, presence: true
13
+
14
+ validates :status, inclusion: STATUSES
15
+ validate :valid_until_validator
16
+
17
+ belongs_to :crew
18
+
19
+ def valid_until_validator
20
+ return unless valid_until && issued_on
21
+
22
+ errors.add(:valid_until, 'cannot be before issue date.') if issued_on >= valid_until
23
+ end
24
+
25
+ def invalidate_other_ids
26
+ return if crew_id.nil?
27
+
28
+ return if status_changed?(from: 'Valid', to: 'Invalid')
29
+
30
+ Ecom::Core::CrewIdCard
31
+ .where(crew_id: crew_id, status: 'Valid')
32
+ .update(status: 'Invalid')
33
+ end
34
+ end
35
+ end
36
+ end
@@ -12,7 +12,7 @@ module Ecom
12
12
  has_one :revision, class_name: 'Ecom::Core::CrewTime', foreign_key: :revision_to_id
13
13
 
14
14
  validates :checkin_time, presence: true, if: :checkout_time
15
- validate :checkout_is_greater_than_checkin
15
+ validate :time_range_validation, :total_time_validation
16
16
 
17
17
  scope :by_attendance, lambda { |id|
18
18
  joins(:attendance_sheet_entry).where(ecom_core_attendance_sheet_entries: { attendance_sheet_id: id })
@@ -20,14 +20,22 @@ module Ecom
20
20
  scope :revised, ->(revised) { where(revised: revised) }
21
21
 
22
22
  before_save :calculate_hours
23
- after_save :calculate_total
23
+ after_save :compute_total_for_entry # :calculate_total
24
24
 
25
- def checkout_is_greater_than_checkin
25
+ def time_range_validation
26
26
  return unless checkin_time && checkout_time && checkout_time <= checkin_time
27
27
 
28
28
  errors.add(:checkout_time, "can't be less than checkin time.")
29
29
  end
30
30
 
31
+ def total_time_validation
32
+ return if checkout_time.nil? || checkin_time.nil?
33
+
34
+ return unless attendance_sheet_entry.total_hours + compute_hours > 8
35
+
36
+ errors.add(:attendance_sheet_entry, 'has more than 8 hours')
37
+ end
38
+
31
39
  def calculate_hours
32
40
  self.hours = if checkout_time.nil? || checkin_time.nil?
33
41
  0
@@ -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
@@ -0,0 +1,6 @@
1
+ module Ecom
2
+ module Core
3
+ class InspectionChecklist < Lookup
4
+ end
5
+ end
6
+ 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,6 @@
1
+ module Ecom
2
+ module Core
3
+ class Material < Lookup
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,21 @@
1
+ module Ecom
2
+ module Core
3
+ class MaterialIdentity < ApplicationRecord
4
+ belongs_to :material_type
5
+ belongs_to :material_sub_type, optional: true
6
+
7
+ validates :material_type_id, :material_type, presence: true
8
+ validate :material_category_hierarchy_validator
9
+
10
+ def material_category_hierarchy_validator
11
+ return if material_type_id.nil? || material_sub_type_id.nil?
12
+
13
+ material_sub_type = MaterialSubType.find_by(id: material_sub_type_id)
14
+
15
+ return unless material_sub_type.material_type_id != material_type_id
16
+
17
+ errors.add(:material_type, 'The given material_sub_type does not belong to the given material_type')
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,18 @@
1
+ module Ecom
2
+ module Core
3
+ class MaterialItem < ApplicationRecord
4
+ before_save :assign_serial_number
5
+
6
+ belongs_to :material_identity
7
+
8
+ validates :serial_number, presence: true, uniqueness: true
9
+ validates :material_identity_id, :material_identity, presence: true
10
+ validates :serial_number, format: { with: /^SN_\d+$/, message: 'Invalid serial number format', multiline: true }
11
+ validates :price, numericality: { greater_than: 0 }, allow_nil: true
12
+
13
+ def assign_serial_number
14
+ self.serial_number = "SN_#{(Time.now.to_f * 1000).to_i}"
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,20 @@
1
+ module Ecom
2
+ module Core
3
+ class MaterialSubType < ApplicationRecord
4
+ after_save :create_material_identity
5
+
6
+ belongs_to :material_type
7
+ has_many :material_identity
8
+
9
+ validates :name, presence: true, uniqueness: { case_sensitive: false, scope: :material_type_id }
10
+ validates :material_type_id, :material_type, presence: true
11
+
12
+ def create_material_identity
13
+ Ecom::Core::MaterialIdentity.create(
14
+ material_type_id: material_type_id,
15
+ material_sub_type_id: id
16
+ )
17
+ end
18
+ end
19
+ end
20
+ 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