cats_core 1.0.4 → 1.0.8

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.
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