jobshop 0.0.131 → 0.0.157

Sign up to get free protection for your applications and to get access to all the features.
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