cats_core 1.0.4 → 1.0.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2d2e957d3c24b295e163776c3ecd67c21e16877687a0c2f4682be641ea2278a5
4
- data.tar.gz: 1230cf3469e3a2943cad4e91d229e5aefb110f84e459b741022e28d91b099c62
3
+ metadata.gz: 45dbf26292f9ebc98fb50e4195e44773184d3f460c0d5aadb3c45afcffffa752
4
+ data.tar.gz: 7cab2b31272ec765c42d8cd8724f1d8f3daff1481fd82057edf08ed40ce389b3
5
5
  SHA512:
6
- metadata.gz: f0b8e1139d6a14e4afc0c49914b2d1cc1f9fe4e6e180410cb15368160f7705ab76665bb8805f37d538207f14d92126a20a70cc7ccbce1b6c4380025a15f06a30
7
- data.tar.gz: 9e1dc4a4ecfd31ce032e64111a5d0d21f4c89a70e0732cfcba688e1e90804e2a9c8cea50ec8a602b7d73fed7d968117ea83c44a0538e436bc4584513823e0b61
6
+ metadata.gz: 6017835a41ce450bb778aea397234083ac257ec74b1f995a5087811dfae0db693172468ceed14c11ef8433dd26eca298436256ff8fc876a7482e8ce2610d46bb
7
+ data.tar.gz: 87ff5af9c3533a23b57ede84a3547818b7f54e51cfb629dbd8f3c26c6de73900f974c2ecb0bc38c999b01bdd29a0b7260daf4ec03510407fae8511d85ae372d3
@@ -1,4 +1,30 @@
1
- module CatsCore
2
- class ApplicationController < ActionController::Base
1
+ module Cats
2
+ module Core
3
+ class ApplicationController < ActionController::Base
4
+ before_action :authenticate
5
+
6
+ def current_user
7
+ return if token.nil?
8
+
9
+ user = Cats::Core::User.find(auth['id'])
10
+ @current_user ||= user
11
+ end
12
+
13
+ def authenticate
14
+ render json: { error: 'Unauthorized' }, status: 401 if current_user.nil?
15
+ end
16
+
17
+ private
18
+
19
+ def token
20
+ return nil if request.env['HTTP_AUTHORIZATION'].nil?
21
+
22
+ request.env['HTTP_AUTHORIZATION'].scan(/Bearer (.*)$/).flatten.last
23
+ end
24
+
25
+ def auth
26
+ Cats::Core::TokenAuthService.decode(token)
27
+ end
28
+ end
3
29
  end
4
30
  end
@@ -0,0 +1,37 @@
1
+ module Cats
2
+ module Core
3
+ class NotificationsController < ApplicationController
4
+ before_action :set_notification, only: %i[mark_as_read mark_as_unread]
5
+ def index
6
+ data = Notification.messages(current_user.notifications.newest_first)
7
+ render json: { success: true, data: data }
8
+ end
9
+
10
+ def read
11
+ data = Notification.messages(current_user.notifications.read.newest_first)
12
+ render json: { success: true, data: data }
13
+ end
14
+
15
+ def unread
16
+ data = Notification.messages(current_user.notifications.unread.newest_first)
17
+ render json: { success: true, data: data }
18
+ end
19
+
20
+ def mark_as_read
21
+ @notification.mark_as_read!
22
+ render json: { success: true, data: @notification.message }
23
+ end
24
+
25
+ def mark_as_unread
26
+ @notification.mark_as_unread!
27
+ render json: { success: true, data: @notification.message }
28
+ end
29
+
30
+ private
31
+
32
+ def set_notification
33
+ @notification = Notification.find(params[:id])
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,18 @@
1
+ module Cats
2
+ module Core
3
+ class RolesController < ApplicationController
4
+ before_action :set_role, only: [:users]
5
+
6
+ def users
7
+ data = ActiveModelSerializers::SerializableResource.new(@role.users)
8
+ render json: { success: true, data: data }
9
+ end
10
+
11
+ private
12
+
13
+ def set_role
14
+ @role = Role.find(params[:id])
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,23 @@
1
+ module Cats
2
+ module Core
3
+ class UsersController < ApplicationController
4
+ before_action :set_user, only: %i[stores warehouses]
5
+
6
+ def stores
7
+ data = ActiveModelSerializers::SerializableResource.new(@user.stores)
8
+ render json: { success: true, data: data }
9
+ end
10
+
11
+ def warehouses
12
+ data = ActiveModelSerializers::SerializableResource.new(@user.warehouses)
13
+ render json: { success: true, data: data }
14
+ end
15
+
16
+ private
17
+
18
+ def set_user
19
+ @user = User.find(params[:id])
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,6 @@
1
+ module Cats
2
+ module Core
3
+ class ApplicationJob < ActiveJob::Base
4
+ end
5
+ end
6
+ end
@@ -12,6 +12,7 @@ module Cats
12
12
  belongs_to :unit_of_measure
13
13
 
14
14
  validates :quantity, presence: true, numericality: { greater_than: 0 }
15
+ validates :volume_per_metric_ton, numericality: { greater_than: 0, allow_nil: true }
15
16
  end
16
17
  end
17
18
  end
@@ -1,7 +1,17 @@
1
1
  module Cats
2
2
  module Core
3
3
  class Notification < ApplicationRecord
4
+ include Noticed::Model
5
+
4
6
  belongs_to :recipient, polymorphic: true
7
+
8
+ def message
9
+ { id: id, read: !read_at.nil? }.merge(to_notification.message)
10
+ end
11
+
12
+ def self.messages(notifications)
13
+ notifications.map(&:message)
14
+ end
5
15
  end
6
16
  end
7
17
  end
@@ -0,0 +1,24 @@
1
+ module Cats
2
+ module Core
3
+ class ReceiptPlan < ApplicationRecord
4
+ # receipt plan status
5
+ DRAFT = 'Draft'.freeze
6
+ ACCEPTED = 'Accepted'.freeze
7
+ DECLINED = 'Declined'.freeze
8
+ RECEIPT_PLAN_STATUSES = [DRAFT, ACCEPTED, DECLINED].freeze
9
+
10
+ validates :quantity, :status, presence: true
11
+ belongs_to :commodity
12
+ belongs_to :plan_recipient, polymorphic: true
13
+ validates :quantity, numericality: { greater_than: 0 }
14
+ validates :status, inclusion: { in: RECEIPT_PLAN_STATUSES }
15
+ validate :validate_reason_for_decline
16
+
17
+ def validate_reason_for_decline
18
+ return unless reason_for_decline.nil? || reason_for_decline == 'Accepted'
19
+
20
+ errors.add(:reason_for_decline, 'must be specified')
21
+ end
22
+ end
23
+ end
24
+ end
@@ -2,6 +2,7 @@ module Cats
2
2
  module Core
3
3
  class Role < ApplicationRecord
4
4
  belongs_to :application_module
5
+ has_and_belongs_to_many :users, join_table: :cats_core_users_cats_core_roles
5
6
  has_many :role_menus
6
7
 
7
8
  validates :name, presence: true
@@ -19,7 +19,8 @@ module Cats
19
19
  validates :quantity, numericality: { greater_than_or_equal_to: 0 }
20
20
  validates :commodity_status, inclusion: { in: Cats::Core::Commodity::COMMODITY_STATUSES }
21
21
  validates :stack_status, inclusion: { in: STACK_STATUSES }
22
- validate :validate_coordinates, :validate_dimensions
22
+ validate :validate_coordinates, :validate_dimensions, :validate_distance_from_wall, :validate_overlap,
23
+ :validate_space_between_stack
23
24
 
24
25
  def validate_coordinates
25
26
  return unless store.present? && length.present? && width.present? && start_x.present? && start_y.present?
@@ -34,6 +35,70 @@ module Cats
34
35
  errors.add(:length, 'cannot exceed store length') if start_x + length > store.length
35
36
  errors.add(:width, 'cannot exceed store width') if start_y + width > store.width
36
37
  end
38
+
39
+ def stacking_rules
40
+ rule = Cats::Core::StackingRule.first
41
+
42
+ # If no rule is defined, then define a default rule
43
+ rule ||= Cats::Core::StackingRule.new(
44
+ space_between_stack: 1,
45
+ distance_from_gangway: 2,
46
+ distance_from_ceiling: 1,
47
+ maximum_height: 5,
48
+ maximum_length: 5,
49
+ maximum_width: 5,
50
+ distance_from_wall: 1
51
+ )
52
+ rule
53
+ end
54
+
55
+ def validate_distance_from_wall
56
+ return unless store.present? && length.present? && width.present? && start_x.present? && start_y.present?
57
+
58
+ rule = stacking_rules
59
+ if start_x < rule.distance_from_wall || store.length - (start_y + length) < rule.distance_from_wall ||
60
+ store.width - (start_x + width) < rule.distance_from_wall || start_y < rule.distance_from_wall
61
+ errors.add(:base,
62
+ message: "The stack must be placed #{rule.distance_from_wall} meter away from a store wall")
63
+ end
64
+ end
65
+
66
+ def validate_overlap
67
+ return unless store.present? && length.present? && width.present? && start_x.present? && start_y.present?
68
+
69
+ stacks = store.stacks.where(stack_status: [RESERVED, ALLOCATED]).where.not(id: id)
70
+ stacks.each do |s|
71
+ errors.add(:base, message: "#{code} overlaps with #{s.code}") if overlaps?(self, s)
72
+ end
73
+ end
74
+
75
+ def validate_space_between_stack
76
+ return unless store.present? && length.present? && width.present? && start_x.present? && start_y.present?
77
+
78
+ stacks = store.stacks.where(stack_status: [RESERVED, ALLOCATED]).where.not(id: id)
79
+ rule = stacking_rules
80
+ new_stack = dup
81
+ new_stack.assign_attributes(start_x: start_x - rule.space_between_stack,
82
+ start_y: start_y - rule.space_between_stack,
83
+ length: length + (2 * rule.space_between_stack),
84
+ width: width + (2 * rule.space_between_stack))
85
+ stacks.each do |s|
86
+ errors.add(:base, message: "#{code} overlaps with #{s.code}") if overlaps?(new_stack, s)
87
+ end
88
+ end
89
+
90
+ # A method that checks if an overlap exists b/n two stacks.
91
+ # An overlap b/n stacks does not occur if one of the stacks is completely above the other stack or
92
+ # if one of the stacks is on the left side of the other stack.
93
+ # If both conditions fail, it means an overlap exists b/n the two stacks
94
+
95
+ def overlaps?(st1, st2)
96
+ return false if st1.start_x > (st2.start_x + st2.width) || st2.start_x > (st1.start_x + st1.width)
97
+
98
+ return false if st1.start_y > (st2.start_y + st2.length) || st2.start_y > (st1.start_y + st1.length)
99
+
100
+ true
101
+ end
37
102
  end
38
103
  end
39
104
  end
@@ -0,0 +1,16 @@
1
+ module Cats
2
+ module Core
3
+ class StackingRule < ApplicationRecord
4
+ validates :distance_from_wall, :space_between_stack, :distance_from_ceiling, :maximum_height, :maximum_length,
5
+ :maximum_width, :distance_from_gangway, presence: true, numericality: { greater_than: 0 }
6
+ validate :validate_only_a_record_exist
7
+
8
+ def validate_only_a_record_exist
9
+ return unless StackingRule.count > 1
10
+
11
+ errors.add(:base,
12
+ 'There is already a stacking rule entry. A new rule can not be added')
13
+ end
14
+ end
15
+ end
16
+ end
@@ -3,6 +3,7 @@ module Cats
3
3
  class Store < ApplicationRecord
4
4
  belongs_to :warehouse, class_name: 'Cats::Core::Location'
5
5
  has_many :stacks
6
+ has_many :receipt_plans, as: :receivable
6
7
 
7
8
  validates :name, :store_keeper_name, :length, :width, :height, presence: true
8
9
  validates :length, :width, :height, numericality: { greater_than: 0 }
@@ -2,7 +2,7 @@ module Cats
2
2
  module Core
3
3
  class User < ApplicationRecord
4
4
  has_secure_password
5
- rolify
5
+ rolify role_cname: 'Cats::Core::Role'
6
6
 
7
7
  belongs_to :application_module
8
8
  has_and_belongs_to_many :roles, join_table: :cats_core_users_cats_core_roles
@@ -11,6 +11,18 @@ module Cats
11
11
  validates :first_name, :last_name, :email, presence: true
12
12
  validates :password, length: { minimum: 6 }
13
13
  validates :email, uniqueness: true, format: { with: URI::MailTo::EMAIL_REGEXP }
14
+
15
+ def warehouses
16
+ return unless details.key?('warehouses')
17
+
18
+ Cats::Core::Location.where(id: details['warehouses'], location_type: Cats::Core::Location::WAREHOUSE)
19
+ end
20
+
21
+ def stores
22
+ return unless details.key?('stores')
23
+
24
+ Cats::Core::Store.where(id: details['stores'])
25
+ end
14
26
  end
15
27
  end
16
28
  end
@@ -0,0 +1,9 @@
1
+ # This is a simple notification class which delivers a
2
+ # message via database delivery method. It requires a
3
+ # +msg+ parameter to instantiate, which represents the
4
+ # text message to deliver.
5
+ class SimpleNotification < Noticed::Base
6
+ deliver_by :database
7
+
8
+ param :msg
9
+ end
@@ -0,0 +1,45 @@
1
+ module Cats
2
+ module Core
3
+ class NotificationService
4
+ NOTIFICATION_RULES = {
5
+ dispatch_plan: {
6
+ application: 'CATS-WH',
7
+ notification: 'DispatchPlanNotification',
8
+ params: [:way_bill],
9
+ recipients: [:warehouse_manager]
10
+ },
11
+ receipt_plan: {
12
+ application: 'CATS-WH',
13
+ notification: 'ReceiptPlanNotification',
14
+ params: [:receipt_plan],
15
+ recipients: [:store_keeper]
16
+ }
17
+ }.freeze
18
+
19
+ def initialize(code, params)
20
+ @code = code
21
+ @params = params
22
+ end
23
+
24
+ def notification_rules
25
+ NOTIFICATION_RULES
26
+ end
27
+
28
+ def create_notifier(rule)
29
+ clazz = rule[:notification].constantize
30
+ clazz.with(**@params)
31
+ end
32
+
33
+ def notify
34
+ rule = notification_rules[@code]
35
+ notifier = create_notifier(rule)
36
+ app_code = rule[:application]
37
+ roles = rule[:recipients]
38
+
39
+ users = Cats::Core::User.joins(:application_module)
40
+ .where(application_module: { prefix: app_code }).with_all_roles(*roles)
41
+ notifier.deliver(users)
42
+ end
43
+ end
44
+ end
45
+ end
data/config/routes.rb CHANGED
@@ -1,2 +1,22 @@
1
1
  Cats::Core::Engine.routes.draw do
2
+ get '/notifications/unread', controller: :notifications, action: :unread
3
+ get '/notifications/read', controller: :notifications, action: :read
4
+
5
+ resources :notifications, only: [:index] do
6
+ member do
7
+ post 'mark_as_read', controller: :notifications, action: :mark_as_read
8
+ post 'mark_as_unread', controller: :notifications, action: :mark_as_unread
9
+ end
10
+ end
11
+ resources :roles do
12
+ member do
13
+ get 'users', controller: :roles, action: :users
14
+ end
15
+ end
16
+ resources :users do
17
+ member do
18
+ get 'stores', controller: :users, action: :stores
19
+ get 'warehouses', controller: :users, action: :warehouses
20
+ end
21
+ end
2
22
  end
@@ -21,6 +21,7 @@ class CreateCatsCoreCommodities < ActiveRecord::Migration[6.1]
21
21
  t.string :description
22
22
  t.boolean :hazardous, null: false, default: false
23
23
  t.date :best_use_before
24
+ t.float :volume_per_metric_ton
24
25
 
25
26
  t.timestamps
26
27
  end
@@ -0,0 +1,15 @@
1
+ class CreateCatsCoreStackingRules < ActiveRecord::Migration[6.1]
2
+ def change
3
+ create_table :cats_core_stacking_rules do |t|
4
+ t.float :distance_from_wall, null: false
5
+ t.float :space_between_stack, null: false
6
+ t.float :distance_from_ceiling, null: false
7
+ t.float :maximum_height, null: false
8
+ t.float :maximum_length, null: false
9
+ t.float :maximum_width, null: false
10
+ t.float :distance_from_gangway, null: false
11
+
12
+ t.timestamps
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ class CreateCatsCoreReceiptPlans < ActiveRecord::Migration[6.1]
2
+ def change
3
+ create_table :cats_core_receipt_plans do |t|
4
+ t.float :quantity, null: false
5
+ t.string :status, default: 'Draft'
6
+ t.references :commodity,
7
+ null: false,
8
+ index: { name: 'commodity_on_receipt_plan_indx' },
9
+ foreign_key: { to_table: :cats_core_commodities }
10
+ t.string :reason_for_decline
11
+ t.references :plan_recipient, polymorphic: true
12
+
13
+ t.timestamps
14
+ end
15
+ end
16
+ end
@@ -1,5 +1,5 @@
1
1
  module Cats
2
2
  module Core
3
- VERSION = '1.0.4'.freeze
3
+ VERSION = '1.0.8'.freeze
4
4
  end
5
5
  end
data/lib/cats_core.rb CHANGED
@@ -3,3 +3,5 @@ require 'cats/core/engine'
3
3
  require 'ancestry'
4
4
  require 'rolify'
5
5
  require 'jwt'
6
+ require 'noticed'
7
+ require 'active_model_serializers'
@@ -8,5 +8,6 @@ FactoryBot.define do
8
8
  quantity { 100 }
9
9
  hazardous { false }
10
10
  best_use_before { Date.today + 2.month }
11
+ volume_per_metric_ton { 10 }
11
12
  end
12
13
  end
@@ -1,7 +1,7 @@
1
1
  FactoryBot.define do
2
2
  factory :notification, class: 'Cats::Core::Notification' do
3
3
  recipient factory: :user
4
- type { nil }
4
+ type { 'ExampleNotification' }
5
5
  params { {} }
6
6
  read_at { Date.today }
7
7
  end
@@ -0,0 +1,9 @@
1
+ FactoryBot.define do
2
+ factory :receipt_plan, class: 'Cats::Core::ReceiptPlan' do
3
+ plan_recipient factory: :store
4
+ commodity
5
+ quantity { 1.5 }
6
+ status { 'Accepted' }
7
+ reason_for_decline { FFaker::Name.name }
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ FactoryBot.define do
2
+ factory :stacking_rule, class: 'Cats::Core::StackingRule' do
3
+ space_between_stack { 1 }
4
+ distance_from_gangway { 1.5 }
5
+ distance_from_ceiling { 1 }
6
+ maximum_height { 2.5 }
7
+ maximum_length { 6 }
8
+ maximum_width { 6 }
9
+ distance_from_wall { 1 }
10
+ end
11
+ end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cats_core
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.4
4
+ version: 1.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Henock L.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-07-24 00:00:00.000000000 Z
11
+ date: 2021-08-05 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: active_model_serializers
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: ancestry
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -203,6 +217,10 @@ files:
203
217
  - README.md
204
218
  - Rakefile
205
219
  - app/controllers/cats/core/application_controller.rb
220
+ - app/controllers/cats/core/notifications_controller.rb
221
+ - app/controllers/cats/core/roles_controller.rb
222
+ - app/controllers/cats/core/users_controller.rb
223
+ - app/jobs/cats/core/application_job.rb
206
224
  - app/models/cats/core/application_module.rb
207
225
  - app/models/cats/core/application_record.rb
208
226
  - app/models/cats/core/commodity.rb
@@ -214,15 +232,19 @@ files:
214
232
  - app/models/cats/core/menu_item.rb
215
233
  - app/models/cats/core/notification.rb
216
234
  - app/models/cats/core/program.rb
235
+ - app/models/cats/core/receipt_plan.rb
217
236
  - app/models/cats/core/role.rb
218
237
  - app/models/cats/core/role_menu.rb
219
238
  - app/models/cats/core/stack.rb
239
+ - app/models/cats/core/stacking_rule.rb
220
240
  - app/models/cats/core/store.rb
221
241
  - app/models/cats/core/transporter.rb
222
242
  - app/models/cats/core/unit_of_measure.rb
223
243
  - app/models/cats/core/user.rb
224
244
  - app/models/cats/core/way_bill.rb
225
245
  - app/models/cats/core/way_bill_item.rb
246
+ - app/notifications/cats/core/simple_notification.rb
247
+ - app/services/cats/core/notification_service.rb
226
248
  - app/services/cats/core/token_auth_service.rb
227
249
  - config/routes.rb
228
250
  - db/migrate/20210715114238_create_cats_core_application_modules.rb
@@ -243,7 +265,9 @@ files:
243
265
  - db/migrate/20210718045516_create_cats_core_way_bills.rb
244
266
  - db/migrate/20210718050751_create_cats_core_way_bill_items.rb
245
267
  - db/migrate/20210718202957_create_cats_core_commodity_transactions.rb
268
+ - db/migrate/20210719133710_create_cats_core_stacking_rules.rb
246
269
  - db/migrate/20210724074657_create_cats_core_notifications.rb
270
+ - db/migrate/20210727074646_create_cats_core_receipt_plans.rb
247
271
  - lib/cats/core.rb
248
272
  - lib/cats/core/engine.rb
249
273
  - lib/cats/core/version.rb
@@ -259,8 +283,10 @@ files:
259
283
  - spec/factories/cats/core/menus.rb
260
284
  - spec/factories/cats/core/notifications.rb
261
285
  - spec/factories/cats/core/programs.rb
286
+ - spec/factories/cats/core/receipt_plans.rb
262
287
  - spec/factories/cats/core/role_menus.rb
263
288
  - spec/factories/cats/core/roles.rb
289
+ - spec/factories/cats/core/stacking_rules.rb
264
290
  - spec/factories/cats/core/stacks.rb
265
291
  - spec/factories/cats/core/stores.rb
266
292
  - spec/factories/cats/core/transporters.rb