jobshop 0.0.131 → 0.0.157

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 (122) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/Rakefile +16 -9
  4. data/app/controllers/concerns/jobshop/authentication_handler.rb +15 -0
  5. data/app/controllers/concerns/jobshop/authorization_handler.rb +29 -0
  6. data/app/controllers/jobshop/application_controller.rb +5 -32
  7. data/app/controllers/jobshop/organizations/lookups_controller.rb +18 -0
  8. data/app/controllers/jobshop/places_controller.rb +1 -1
  9. data/app/controllers/jobshop/{welcome/registrations_controller.rb → registrations_controller.rb} +2 -7
  10. data/app/controllers/jobshop/session_activations_controller.rb +1 -1
  11. data/app/controllers/jobshop/sessions_controller.rb +15 -1
  12. data/app/controllers/jobshop/users_controller.rb +9 -0
  13. data/app/mailers/jobshop/application_mailer.rb +2 -2
  14. data/app/models/jobshop/ability.rb +7 -0
  15. data/app/models/jobshop/collection.rb +22 -0
  16. data/app/models/jobshop/company.rb +22 -0
  17. data/app/models/jobshop/dashboard.rb +1 -1
  18. data/app/models/jobshop/inspection/boolean_criterion.rb +44 -0
  19. data/app/models/jobshop/inspection/criterion.rb +22 -0
  20. data/app/models/jobshop/inspection/deviation_criterion.rb +112 -0
  21. data/app/models/jobshop/inspection/limit_criterion.rb +114 -0
  22. data/app/models/jobshop/inspection/report.rb +41 -0
  23. data/app/models/jobshop/inspection/result.rb +42 -0
  24. data/app/models/jobshop/inspection/tuple.rb +13 -0
  25. data/app/models/jobshop/inspection.rb +13 -0
  26. data/app/models/jobshop/order.rb +41 -0
  27. data/app/models/jobshop/order_line.rb +13 -0
  28. data/app/models/jobshop/organization.rb +34 -0
  29. data/app/models/jobshop/place.rb +6 -4
  30. data/app/models/jobshop/product.rb +23 -0
  31. data/app/models/jobshop/registration.rb +14 -10
  32. data/app/models/jobshop/role.rb +24 -0
  33. data/app/models/jobshop/role_ability.rb +7 -0
  34. data/app/models/jobshop/role_assignment.rb +7 -0
  35. data/app/models/jobshop/routing_process.rb +20 -0
  36. data/app/models/jobshop/routing_step.rb +11 -0
  37. data/app/models/jobshop/thing.rb +30 -4
  38. data/app/models/jobshop/user.rb +23 -25
  39. data/app/serializers/jobshop/test_user_serializer.rb +10 -0
  40. data/app/services/jobshop/authentication_service.rb +20 -0
  41. data/app/services/jobshop/authorization_service.rb +30 -0
  42. data/app/services/jobshop/jwt_service.rb +17 -0
  43. data/config/routes.rb +2 -43
  44. data/db/migrate/20170311194758_initialize_jobshop.rb +1 -106
  45. data/db/migrate/20171216021339_create_organizations.rb +20 -0
  46. data/db/migrate/20171216021717_create_users.rb +30 -0
  47. data/db/migrate/20171216021853_create_companies.rb +25 -0
  48. data/db/migrate/20171216022020_create_places.rb +22 -0
  49. data/db/migrate/20171216022135_create_products.rb +27 -0
  50. data/db/migrate/20171216022605_create_orders.rb +53 -0
  51. data/db/migrate/20171216023018_create_roles.rb +52 -0
  52. data/db/migrate/20171216023022_create_sessions.rb +21 -0
  53. data/db/migrate/20171216035357_create_things.rb +44 -0
  54. data/db/migrate/20171219022118_create_routing_processes.rb +57 -0
  55. data/db/migrate/20180107203241_create_inspection.rb +159 -0
  56. data/{app/assets/stylesheets/jobshop/welcome.scss → db/seeds.rb} +0 -0
  57. data/lib/generators/jobshop/app/app_generator.rb +36 -3
  58. data/lib/generators/jobshop/app/templates/config/database.yml.tt +19 -0
  59. data/lib/generators/jobshop/config/templates/config/initializers/jobshop.rb.tt +0 -2
  60. data/lib/generators/jobshop/dummy/dummy_generator.rb +3 -14
  61. data/lib/jobshop/cli/spinner.rb +21 -0
  62. data/lib/jobshop/cli.rb +44 -24
  63. data/lib/jobshop/configuration.rb +24 -0
  64. data/lib/jobshop/dummy_app.rb +155 -0
  65. data/lib/jobshop/engine.rb +30 -31
  66. data/lib/jobshop/helpers/migration.rb +55 -0
  67. data/lib/jobshop/version.rb +2 -2
  68. data/lib/jobshop.rb +7 -21
  69. data/lib/tasks/jobshop_tasks.rake +7 -24
  70. metadata +113 -150
  71. data/app/assets/config/jobshop_manifest.js +0 -2
  72. data/app/assets/images/jobshop/logo.svg +0 -50
  73. data/app/assets/javascripts/jobshop/application.js +0 -34
  74. data/app/assets/stylesheets/jobshop/application.scss +0 -57
  75. data/app/assets/stylesheets/jobshop/breakpoints.scss +0 -47
  76. data/app/assets/stylesheets/jobshop/dialog.scss +0 -43
  77. data/app/controllers/concerns/registration_token_validation.rb +0 -41
  78. data/app/controllers/jobshop/teams/lookups_controller.rb +0 -20
  79. data/app/controllers/jobshop/teams_controller.rb +0 -6
  80. data/app/controllers/jobshop/welcome/places_controller.rb +0 -42
  81. data/app/controllers/jobshop/welcome/things_controller.rb +0 -42
  82. data/app/controllers/jobshop/welcome_controller.rb +0 -11
  83. data/app/helpers/jobshop/application_helper.rb +0 -14
  84. data/app/mailers/jobshop/teams_mailer.rb +0 -10
  85. data/app/models/jobshop/team.rb +0 -36
  86. data/app/policies/jobshop/application_policy.rb +0 -55
  87. data/app/views/devise/confirmations/new.html.haml +0 -10
  88. data/app/views/devise/mailer/confirmation_instructions.html.haml +0 -4
  89. data/app/views/devise/mailer/password_change.html.haml +0 -3
  90. data/app/views/devise/mailer/reset_password_instructions.html.haml +0 -6
  91. data/app/views/devise/mailer/unlock_instructions.html.haml +0 -5
  92. data/app/views/devise/passwords/edit.html.haml +0 -19
  93. data/app/views/devise/passwords/new.html.haml +0 -10
  94. data/app/views/devise/registrations/edit.html.haml +0 -31
  95. data/app/views/devise/registrations/new.html.haml +0 -21
  96. data/app/views/devise/sessions/new.html.haml +0 -15
  97. data/app/views/devise/shared/_links.html.haml +0 -19
  98. data/app/views/devise/unlocks/new.html.haml +0 -10
  99. data/app/views/jobshop/dashboards/show.html.haml +0 -140
  100. data/app/views/jobshop/places/show.html.haml +0 -12
  101. data/app/views/jobshop/session_activations/destroy.js.erb +0 -1
  102. data/app/views/jobshop/shared/_authenticated_header.html.haml +0 -8
  103. data/app/views/jobshop/shared/_unauthenticated_header.html.haml +0 -10
  104. data/app/views/jobshop/teams/lookups/show.html.haml +0 -14
  105. data/app/views/jobshop/teams_mailer/found_teams.html.haml +0 -11
  106. data/app/views/jobshop/teams_mailer/found_teams.text.erb +0 -8
  107. data/app/views/jobshop/welcome/index.html.haml +0 -21
  108. data/app/views/jobshop/welcome/places/new.html.haml +0 -22
  109. data/app/views/jobshop/welcome/registrations/new.html.haml +0 -32
  110. data/app/views/jobshop/welcome/things/new.html.haml +0 -24
  111. data/app/views/layouts/jobshop/application.html.haml +0 -15
  112. data/app/views/layouts/jobshop/unauthenticated.html.haml +0 -6
  113. data/config/initializers/assets.rb +0 -11
  114. data/config/initializers/devise.rb +0 -282
  115. data/config/initializers/field_with_errors.rb +0 -3
  116. data/config/initializers/simple_form.rb +0 -157
  117. data/config/locales/devise.en.yml +0 -62
  118. data/config/locales/simple_form.en.yml +0 -31
  119. data/db/migrate/keep +0 -0
  120. data/lib/generators/jobshop/team/USAGE +0 -7
  121. data/lib/generators/jobshop/team/team_generator.rb +0 -60
  122. data/lib/jobshop/failure_app.rb +0 -11
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6d4ffcb8337b5be5554bd4cea94434f222066ae8
4
- data.tar.gz: e8783e7dcea6eac31286535aeba6b23e2b150546
3
+ metadata.gz: cfebb1f31d1fcbb60de9a9b6d74d38cf33db9dd8
4
+ data.tar.gz: 06d074ee3efe7275a4a2704b0c4ad1a512853259
5
5
  SHA512:
6
- metadata.gz: d1aac609e64912ccdeb7b2bb523080351be1810d2de82d0afaeb1b5e5d5e2df25341c2c6a86e63de1b3ddae799e9c0339495dc49afc6aafd17d1f3e55c6abda1
7
- data.tar.gz: c862c37ce01afafeda6f538415d4b6a94ea6e31f2ced2388778789b8b3823975b8412e7bd638ebac6b7c2cf481b720305dea83d985bc3225f3f1a89250b5ee0f
6
+ metadata.gz: 342ba401730334f6e0b99959c2a90abb10b1f5a46dc5caf88c36df26515797d510944035742c133cd38e244f551df4235ed15bd6774cf0ac022594807be1f138
7
+ data.tar.gz: c6186e7da358301f38968e0a700bab329a4a98be4f57934163513b005cad73f31370184489f2ec10367a90cdfb0d0f6c5d74c100a437937b19aa3ab9ccd6f38d
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Jobshop
2
2
 
3
- Real-time production tracking and process evaluation in the cloud - or under your own roof.
3
+ Real-time production tracking and process evaluation, under your own roof.
4
4
 
5
5
  [![Gem Version](https://badge.fury.io/rb/jobshop.svg)](https://badge.fury.io/rb/jobshop)
6
6
  [![Code Climate](https://codeclimate.com/github/jobshop/jobshop/badges/gpa.svg)](https://codeclimate.com/github/jobshop/jobshop)
data/Rakefile CHANGED
@@ -1,12 +1,9 @@
1
- #!/usr/bin/env rake
2
-
3
1
  begin
4
2
  require "bundler/setup"
5
3
  rescue LoadError
6
4
  puts "You must `gem install bundler` and `bundle install` to run rake tasks"
7
5
  end
8
6
 
9
- require "rails/version"
10
7
  require "rdoc/task"
11
8
 
12
9
  RDoc::Task.new(:rdoc) do |rdoc|
@@ -18,17 +15,27 @@ RDoc::Task.new(:rdoc) do |rdoc|
18
15
  end
19
16
 
20
17
  require "jobshop/dummy_app"
18
+ load "rails/tasks/statistics.rake"
21
19
 
22
20
  APP_RAKEFILE = Jobshop::DummyApp.rakefile
21
+ require "bundler/gem_tasks"
23
22
 
24
23
  load "rails/tasks/engine.rake" if File.exist?(APP_RAKEFILE)
25
- load "rails/tasks/statistics.rake"
26
24
 
27
- namespace :jobshop do
28
- desc "Create dummy app"
29
- task :dummy do
30
- Jobshop::DummyApp.new
25
+ Bundler::GemHelper.install_tasks
26
+
27
+ require "rspec/core"
28
+ require "rspec/core/rake_task"
29
+
30
+ desc "Verify that all FactoryBot factories are valid"
31
+ task lint: :environment do
32
+ DatabaseCleaner.cleaning do
33
+ FactoryBot.definition_file_paths = Jobshop::DummyApp.factory_paths
34
+ FactoryBot.find_definitions
35
+ FactoryBot.lint traits: true
31
36
  end
32
37
  end
33
38
 
34
- Bundler::GemHelper.install_tasks
39
+ desc "Run all specs"
40
+ RSpec::Core::RakeTask.new(:spec)
41
+ task default: :spec
@@ -0,0 +1,15 @@
1
+ module Jobshop
2
+ module AuthenticationHandler
3
+ extend ActiveSupport::Concern
4
+
5
+ class InvalidCredentials < StandardError; end
6
+
7
+ included do
8
+ rescue_from AuthenticationHandler::InvalidCredentials, with: :unauthorized
9
+ end
10
+
11
+ private def unauthorized(e)
12
+ render({ json: { message: "Invalid credentials #{e.message}" } }, :unauthorized)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,29 @@
1
+ module Jobshop
2
+ module AuthorizationHandler
3
+ extend ActiveSupport::Concern
4
+
5
+ class TokenMissing < StandardError; end
6
+ class TokenInvalid < StandardError; end
7
+
8
+ included do
9
+ before_action :authorize_request
10
+
11
+ rescue_from Jobshop::AuthorizationHandler::TokenMissing, with: :four_twenty_two
12
+ rescue_from Jobshop::AuthorizationHandler::TokenInvalid, with: :four_twenty_two
13
+ rescue_from ActiveRecord::RecordInvalid, with: :four_twenty_two
14
+ rescue_from ActiveRecord::RecordNotFound do |e|
15
+ render({ json: { message: e.message } }, :not_found)
16
+ end
17
+
18
+ attr_reader :current_user
19
+ end
20
+
21
+ private def four_twenty_two(e)
22
+ render({ json: { message: e.message } }, :unprocessable_entity)
23
+ end
24
+
25
+ private def authorize_request
26
+ @current_user = (AuthorizationService.new(request.headers).perform)[:user]
27
+ end
28
+ end
29
+ end
@@ -1,37 +1,10 @@
1
- # Parts of this class borrowed from:
2
- # https://gist.github.com/josevalim/fb706b1e933ef01e4fb6
3
- # Thank you Jose Valim!
4
-
5
1
  module Jobshop
6
- class ApplicationController < ActionController::Base
7
- layout :layout_for_application
8
-
9
- protect_from_forgery
10
-
11
- before_action EmailTokenValidation
12
- before_action :authenticate_user!
13
-
14
- def after_sign_in_path_for(resource_or_scope)
15
- if !resource_or_scope.onboard?
16
- welcome_path
17
- else
18
- super
19
- end
20
- end
21
-
22
- private
23
-
24
- def current_team
25
- @current_team ||= current_user && current_user.team
26
- end; helper_method :current_team
2
+ class ApplicationController < ActionController::API
3
+ include Jobshop::AuthorizationHandler
4
+ include Jobshop::AuthenticationHandler
27
5
 
28
- def layout_for_application
29
- if devise_controller? && controller_name == "sessions" ||
30
- controller_path == "jobshop/teams/lookups"
31
- "jobshop/unauthenticated"
32
- else
33
- "jobshop/application"
34
- end
6
+ def current_organization
7
+ @current_organization ||= current_user && current_user.organization
35
8
  end
36
9
  end
37
10
  end
@@ -0,0 +1,18 @@
1
+ require_dependency "jobshop/application_controller"
2
+
3
+ module Jobshop
4
+ class Organizations::LookupsController < ApplicationController
5
+ def show
6
+ @lookup = Jobshop::User.new
7
+ end
8
+
9
+ def create
10
+ emails = params[:user][:email].split(",").map(&:strip).take(5)
11
+ Jobshop::Organization.grouped_by_email(emails).each_pair do |email, organizations|
12
+ Jobshop::OrganizationsMailer.found_organizations(email, organizations).deliver_now
13
+ end
14
+
15
+ redirect_to organizations_lookup_path
16
+ end
17
+ end
18
+ end
@@ -7,6 +7,6 @@ module Jobshop
7
7
 
8
8
  private def session_activations
9
9
  current_user.session_activations
10
- end; helper_method :session_activations
10
+ end
11
11
  end
12
12
  end
@@ -1,15 +1,10 @@
1
1
  require_dependency "jobshop/application_controller"
2
2
 
3
3
  module Jobshop
4
- class Welcome::RegistrationsController < ApplicationController
5
- skip_before_action :authenticate_user!
6
-
7
- before_action RegistrationTokenValidation
8
-
9
- layout "jobshop/unauthenticated"
10
-
4
+ class RegistrationsController < ApplicationController
11
5
  def new
12
6
  @registration = ::Jobshop::Registration.new(params)
7
+ render json: @registration
13
8
  end
14
9
 
15
10
  def create
@@ -8,6 +8,6 @@ module Jobshop
8
8
 
9
9
  private def revoked_session
10
10
  @revoked_session ||= current_user.session_activations.find(params[:id])
11
- end; helper_method :revoked_session
11
+ end
12
12
  end
13
13
  end
@@ -1,6 +1,20 @@
1
1
  require_dependency "jobshop/application_controller"
2
2
 
3
3
  module Jobshop
4
- class SessionsController < Devise::SessionsController
4
+ class SessionsController < ApplicationController
5
+ skip_before_action :authorize_request, only: :new
6
+
7
+ def new
8
+ token = AuthenticationService.new(
9
+ session_params[:email],
10
+ session_params[:password]
11
+ ).perform
12
+
13
+ render(json: { auth_token: token })
14
+ end
15
+
16
+ private def session_params
17
+ params.permit(:email, :password)
18
+ end
5
19
  end
6
20
  end
@@ -0,0 +1,9 @@
1
+ require_dependency "jobshop/application_controller"
2
+
3
+ module Jobshop
4
+ class UsersController < ApplicationController
5
+ def current
6
+ render(json: Jobshop::TestUserSerializer.new(current_user))
7
+ end
8
+ end
9
+ end
@@ -1,8 +1,8 @@
1
1
  module Jobshop
2
2
  class ApplicationMailer < ActionMailer::Base
3
- default from: "jobshop-teams-lookup@example.com"
3
+ default from: "jobshop-lookup@example.com"
4
4
 
5
- layout "jobshop/mailer"
5
+ layout "mailer"
6
6
  end
7
7
  end
8
8
 
@@ -0,0 +1,7 @@
1
+ module Jobshop
2
+ class Ability < ApplicationRecord
3
+ belongs_to :organization, class_name: "Jobshop::Organization"
4
+ has_many :role_abilities, -> (object) { where(organization_id: object.organization_id) }
5
+ has_many :roles, through: :role_abilities
6
+ end
7
+ end
@@ -0,0 +1,22 @@
1
+ module Jobshop
2
+ class Collection < ApplicationRecord
3
+ self.primary_keys = [ :organization_id, :collection_id ]
4
+
5
+ after_create do
6
+ self.class.connection.clear_query_cache
7
+ fresh_collection = self.class.unscoped {
8
+ self.class.find_by!(organization: organization, name: name)
9
+ }
10
+ @attributes = fresh_collection.instance_variable_get(:@attributes)
11
+ @new_record = false
12
+ self
13
+ end
14
+
15
+ belongs_to :organization, inverse_of: :collections
16
+ has_many :things, inverse_of: :collection,
17
+ foreign_key: [ :organization_id, :collection_id ]
18
+
19
+ validates :name, presence: true,
20
+ uniqueness: { scope: :organization_id, case_sensitive: false }
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ module Jobshop
2
+ class Company < ApplicationRecord
3
+ self.primary_keys = [ :organization_id, :company_id ]
4
+
5
+ after_create do
6
+ self.class.connection.clear_query_cache
7
+ @attributes = self.class.unscoped {
8
+ self.class.find_by!(organization: organization, name: name)
9
+ }.instance_variable_get(:@attributes)
10
+ @new_record = false
11
+ self
12
+ end
13
+
14
+ belongs_to :organization, inverse_of: :companies
15
+ belongs_to :created_by, class_name: "Jobshop::User",
16
+ foreign_key: [ :organization_id, :created_by_id ]
17
+ has_many :orders, inverse_of: :company,
18
+ foreign_key: [ :organization_id, :company_id ]
19
+
20
+ validates :name, presence: true
21
+ end
22
+ end
@@ -1,5 +1,5 @@
1
1
  module Jobshop
2
2
  class Dashboard < ApplicationRecord
3
- belongs_to :team
3
+ belongs_to :organization
4
4
  end
5
5
  end
@@ -0,0 +1,44 @@
1
+ module Jobshop
2
+ class Inspection::BooleanCriterion < ApplicationRecord
3
+ self.primary_keys = [ :organization_id, :report_id, :criterion_id, :criterion_type ]
4
+
5
+ after_initialize do
6
+ self[:criterion_id] ||= SecureRandom.uuid if new_record?
7
+ end
8
+
9
+ belongs_to :organization
10
+ has_one :criterion, as: :criterion, autosave: true, dependent: :destroy,
11
+ foreign_key: [ :organization_id, :report_id, :criterion_id, :criterion_type ]
12
+ belongs_to :report, class_name: "Jobshop::Inspection::Report",
13
+ foreign_key: [ :organization_id, :report_id ], inverse_of: :criteria
14
+
15
+ alias_method :criterion_without_build, :criterion
16
+ def criterion_with_build
17
+ criterion_without_build || build_criterion(criterion_id: criterion_id, report: report)
18
+ end; alias_method :criterion, :criterion_with_build
19
+
20
+ delegate :name, :name=, :position, :position=, to: :criterion
21
+
22
+ def specification
23
+ @specification ||= "#{condition}\nPASS/FAIL"
24
+ end
25
+
26
+ def tolerance
27
+ @tolerance ||= condition
28
+ end
29
+
30
+ def unit
31
+ "boolean".freeze
32
+ end
33
+
34
+ def pass?(value)
35
+ ![ false, 0, "0", "f", "F", "false", "FALSE", "off", "OFF" ].include?(value)
36
+ end
37
+
38
+ # Generate a random value that has a 90% chance of being in spec.
39
+ # TODO: This needs to go in the tests somewhere, not really in the model.
40
+ def random
41
+ rand(100) < 10
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,22 @@
1
+ module Jobshop
2
+ class Inspection::Criterion < ApplicationRecord
3
+ self.primary_keys = [ :organization_id, :report_id, :criterion_id, :criterion_type ]
4
+
5
+ after_create do
6
+ self.class.connection.clear_query_cache
7
+ @attributes = self.class.unscoped {
8
+ self.class.find_by!(organization: organization, criterion_id: criterion_id)
9
+ }.instance_variable_get(:@attributes)
10
+ @new_record = false
11
+ self
12
+ end
13
+
14
+ belongs_to :organization
15
+ belongs_to :criterion, polymorphic: true, optional: true, dependent: :destroy,
16
+ foreign_key: [ :organization_id, :report_id, :criterion_id, :criterion_type ]
17
+ belongs_to :report, class_name: "Jobshop::Inspection::Report",
18
+ foreign_key: [ :organization_id, :report_id ], inverse_of: :criteria
19
+
20
+ delegate :ascii, :pass?, :random, :unit, to: :criterion
21
+ end
22
+ end
@@ -0,0 +1,112 @@
1
+ module Jobshop
2
+ class Inspection::DeviationCriterion < ApplicationRecord
3
+ self.primary_keys = [ :organization_id, :report_id, :criterion_id, :criterion_type ]
4
+
5
+ after_initialize do
6
+ self.criterion_id ||= SecureRandom.uuid if new_record?
7
+ end
8
+
9
+ belongs_to :organization
10
+ belongs_to :report, class_name: "Jobshop::Inspection::Report",
11
+ foreign_key: [ :organization_id, :report_id ], inverse_of: :criteria
12
+ has_one :criterion, as: :criterion, autosave: true, dependent: :destroy,
13
+ foreign_key: [ :organization_id, :report_id, :criterion_id, :criterion_type ]
14
+
15
+ alias_method :criterion_without_build, :criterion
16
+ def criterion_with_build
17
+ criterion_without_build || build_criterion
18
+ end; alias_method :criterion, :criterion_with_build
19
+
20
+ delegate :name, :name=, :position, :position=, to: :criterion
21
+
22
+ validates :nominal, presence: true
23
+ validates :lower, presence: true
24
+ validates :upper, presence: true
25
+
26
+ validate :lower_less_than_upper
27
+
28
+ def mean
29
+ ((nominal + lower) + (nominal + upper)) / 2
30
+ end
31
+
32
+ def nominal
33
+ self[:nominal] && Unitwise(self[:nominal].truncate(4), self[:unit])
34
+ end
35
+
36
+ def lower
37
+ self[:lower] && Unitwise(self[:lower].truncate(4), self[:unit])
38
+ end
39
+
40
+ def lower=(value)
41
+ self[:lower] = if value.respond_to?(:unit)
42
+ value.convert_to(self[:unit]).value
43
+ else
44
+ value
45
+ end
46
+ end
47
+
48
+ def upper
49
+ self[:upper] && Unitwise(self[:upper].truncate(4), self[:unit])
50
+ end
51
+
52
+ def upper=(value)
53
+ self[:upper] = if value.respond_to?(:unit)
54
+ value.convert_to(self[:unit]).value
55
+ else
56
+ value
57
+ end
58
+ end
59
+
60
+ def lower_less_than_upper
61
+ return unless lower && upper
62
+ if lower > upper
63
+ errors[:base] << "minimum must be less than maximum"
64
+ end
65
+ end
66
+
67
+ def specification
68
+ @specfication ||= if symmetric?
69
+ "#{nominal}±#{abs(upper)}"
70
+ else
71
+ "#{nominal}\n#{lower}/#{upper}"
72
+ end
73
+ end
74
+
75
+ def symmetric?
76
+ @symmetrical ||= lower == upper * -1
77
+ end
78
+
79
+ def pass?(value)
80
+ !undersize?(value) && !oversize?(value)
81
+ end
82
+
83
+ # Generate a random value that has a 90% chance of being in spec.
84
+ # TODO: This needs to go in the tests somewhere, not really in the model.
85
+ def random
86
+ rand_lower = nominal.to_f + lower.to_f * 1.1
87
+ rand_upper = nominal.to_f + upper.to_f * 1.1
88
+ value = rand(rand_lower..rand_upper)
89
+ Unitwise(value.truncate(4), self[:unit])
90
+ end
91
+
92
+ private def oversize?(value)
93
+ value = if value.respond_to?(:unit)
94
+ value.convert_to(self[:unit])
95
+ else
96
+ Unitwise(value, self[:unit])
97
+ end
98
+
99
+ value > upper || false
100
+ end
101
+
102
+ private def undersize?(value)
103
+ value = if value.respond_to?(:unit)
104
+ value.convert_to(self[:unit])
105
+ else
106
+ Unitwise(value, self[:unit])
107
+ end
108
+
109
+ value < nominal - lower || false
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,114 @@
1
+ module Jobshop
2
+ class Inspection::LimitCriterion < ApplicationRecord
3
+ self.primary_keys = [ :organization_id, :report_id, :criterion_id, :criterion_type ]
4
+
5
+ after_initialize do
6
+ self.criterion_id = SecureRandom.uuid if new_record?
7
+ end
8
+
9
+ belongs_to :organization
10
+ has_one :criterion, as: :criterion, autosave: true, dependent: :destroy, inverse_of: :criterion,
11
+ foreign_key: [ :organization_id, :report_id, :criterion_id, :criterion_type ]
12
+ belongs_to :report, class_name: "Jobshop::Inspection::Report",
13
+ foreign_key: [ :organization_id, :report_id ], inverse_of: :criteria
14
+
15
+ def criterion
16
+ super || build_criterion
17
+ end
18
+
19
+ delegate :name, :name=, :position, :position=, to: :criterion
20
+
21
+ validate :minimum_or_maximum_or_both,
22
+ :minimum_less_than_maximum
23
+
24
+ def minimum
25
+ self[:minimum] && Unitwise(self[:minimum].truncate(4), self[:unit])
26
+ end
27
+
28
+ def minimum=(value)
29
+ if value && value.respond_to?(:unit)
30
+ self[:minimum] = value.value
31
+ self[:unit] = value.unit
32
+ else
33
+ self[:minimum] = value
34
+ end
35
+ end
36
+
37
+ def maximum
38
+ self[:maximum] && Unitwise(self[:maximum].truncate(4), self[:unit])
39
+ end
40
+
41
+ def maximum=(value)
42
+ if value && value.respond_to?(:unit)
43
+ self[:maximum] = value.value
44
+ self[:unit] = value.unit
45
+ else
46
+ self[:maximum] = value
47
+ end
48
+ end
49
+
50
+ def specification
51
+ @specification ||= [
52
+ qualifier, [ minimum, maximum ].compact.join("/")
53
+ ].compact.join("\n")
54
+ end
55
+
56
+ def qualifier
57
+ @qualifier ||= if minimum && !maximum
58
+ "Minimum"
59
+ elsif maximum && !minimum
60
+ "Maximum"
61
+ else
62
+ nil
63
+ end
64
+ end
65
+
66
+ def unbound?
67
+ !minimum ^ !maximum
68
+ end
69
+
70
+ def bound?
71
+ !unbound?
72
+ end
73
+
74
+ def pass?(value)
75
+ !undersize?(value) && !oversize?(value)
76
+ end
77
+
78
+ private def oversize?(value)
79
+ value = Unitwise(value, self[:unit]) unless value.respond_to?(:unit)
80
+ maximum && value > maximum || false
81
+ end
82
+
83
+ private def undersize?(value)
84
+ value = Unitwise(value, self[:unit]) unless value.respond_to?(:unit)
85
+ minimum && value < minimum || false
86
+ end
87
+
88
+ # Generate a random value that has a 90% chance of being in spec.
89
+ # TODO: This needs to go in the tests somewhere, not really in the model.
90
+ def random
91
+ value = if bound?
92
+ rand(self[:minimum].to_f..self[:maximum].to_f) * 1.1
93
+ elsif maximum
94
+ rand(self[:maximum].to_f) * 1.1
95
+ elsif minimum
96
+ rand((self[:minimum].to_f * 0.8)..(self[:minimum].to_f * 2.0))
97
+ end
98
+
99
+ Unitwise(value, self[:unit])
100
+ end
101
+
102
+ private def minimum_or_maximum_or_both
103
+ unless minimum || maximum
104
+ errors[:base] << "must specify minimum, maximum or both"
105
+ end
106
+ end
107
+
108
+ private def minimum_less_than_maximum
109
+ if(minimum && maximum && minimum > maximum)
110
+ errors[:base] << "minimum must be less than maximum"
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,41 @@
1
+ module Jobshop
2
+ class Inspection::Report < ApplicationRecord
3
+ self.primary_keys = [ :organization_id, :report_id ]
4
+
5
+ after_initialize do
6
+ self.report_id ||= SecureRandom.uuid if new_record?
7
+ end
8
+
9
+ after_create do
10
+ self.class.connection.clear_query_cache
11
+ @attributes = self.class.unscoped {
12
+ self.class.find_by!(organization: organization, report_id: report_id)
13
+ }.instance_variable_get(:@attributes)
14
+ @new_record = false
15
+ self
16
+ end
17
+
18
+ belongs_to :organization
19
+ has_many :criteria, -> { includes(:criterion).order(:position) },
20
+ foreign_key: [ :organization_id, :report_id ], inverse_of: :report
21
+ has_many :tuples, -> { order(:position) }, inverse_of: :report,
22
+ foreign_key: [ :organization_id, :report_id ]
23
+
24
+ def ascii
25
+ require "terminal-table"
26
+ table = Terminal::Table.new do |t|
27
+ t.title = "Inspection Report"
28
+ t << [ "Feature" ].concat(criteria.map(&:criterion).map(&:name))
29
+ t << [ "Specification" ].concat(criteria.map(&:criterion).map(&:specification))
30
+ tuples.each do |tuple|
31
+ results = criteria.map do |criterion|
32
+ tuple.results.find_by(criterion: criterion)
33
+ end
34
+ t << [ tuple.position ].concat(results.map(&:value))
35
+ end
36
+ end
37
+
38
+ puts table
39
+ end
40
+ end
41
+ end