cats_core 1.0.48 → 1.1.1

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: e0cb918631b3c8651aca92abc3209f968ce85fbb0e6408332ae5683b95130333
4
- data.tar.gz: 3dca9a7897114e6bf7830951190ac504eaceb03e2f9bf1e3f7de1c5bd1158a5a
3
+ metadata.gz: e1410fa75e3f9e4eb96e4c9df8cbd6bb358bb56e4cce6ef42b095aff3b6331b1
4
+ data.tar.gz: c1529759ffa5ba650bd31df10ae302943afc14e876fff0710a35144cf0841981
5
5
  SHA512:
6
- metadata.gz: a8d658d04da98b1e5607be0843fdfde3663d7ea089b56b9fd7f073420ffa00715613426233c066ca1a92f503bfd8bbc8b9a91663f633ddb93b3e779d1546496a
7
- data.tar.gz: 4aa0772c1ca60ec644476785dc96b3fe7639fcb77e2a97d76e6d757f0a44c6a8f8beb6738edd15361e4edeb41c6559f2ae9f6186bd20e601358fcf1cf1092ab9
6
+ metadata.gz: 9b31f50e3f08e9a65c1da38520f3afef988613654e5250607e48761274951d3be4975e6b47c704c65ad6211d03081ab1c60b3f6ae1eea6fae191d498bd021110
7
+ data.tar.gz: 4046501c4c360769d9bc23628f4e067b8c7b55d95f898d57fda4f202f75ddc73fa5d5a60e5f163cf37ab340024b12a28065bb80f8be9eb5f5fb64d81151e8863
@@ -0,0 +1,13 @@
1
+ module Cats
2
+ module Core
3
+ class DispatchTransactionsController < ApplicationController
4
+ include Common
5
+
6
+ private
7
+
8
+ def model_params
9
+ params.require(:payload).permit(:source_id, :destination_id, :transaction_date, :quantity)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,75 @@
1
+ module Cats
2
+ module Core
3
+ class DispatchesController < ApplicationController
4
+ include Common
5
+
6
+ before_action :set_service, only: %i[create_receipt_authorization approve start search confirm]
7
+ before_action :set_dispatch, only: %i[approve start confirm]
8
+
9
+ def index
10
+ dispatches = Cats::Core::Dispatch.where(allocation_item_id: params[:id])
11
+ render json: { success: true, data: serialize(dispatches) }
12
+ end
13
+
14
+ def create
15
+ dispatch = Cats::Core::Dispatch.new(model_params)
16
+ dispatch.dispatch_status = Cats::Core::Dispatch::DRAFT
17
+ dispatch.prepared_by = current_user
18
+ if dispatch.save
19
+ render json: { success: true, data: serialize(dispatch) }, status: :created
20
+ else
21
+ render json: { success: false, error: dispatch.errors.full_messages[0] }, status: :unprocessable_entity
22
+ end
23
+ end
24
+
25
+ def create_receipt_authorization
26
+ allocation_item = Cats::Core::AllocationItem.find(params[:id])
27
+ dispatch = @service.create_receipt_authorization(
28
+ params[:reference_no], allocation_item, params[:quantity], params[:remark], current_user
29
+ )
30
+ render json: { success: true, data: serialize(dispatch) }
31
+ end
32
+
33
+ def approve
34
+ dispatch = @service.approve(@dispatch)
35
+ render json: { success: true, data: serialize(dispatch) }
36
+ rescue StandardError => e
37
+ render json: { success: false, error: e.message }
38
+ end
39
+
40
+ def start
41
+ dispatch = @service.start(@dispatch)
42
+ render json: { success: true, data: serialize(dispatch) }
43
+ rescue StandardError => e
44
+ render json: { success: false, error: e.message }
45
+ end
46
+
47
+ def confirm
48
+ dispatch = @service.confirm(@dispatch)
49
+ render json: { success: true, data: serialize(dispatch) }
50
+ rescue StandardError => e
51
+ render json: { success: false, error: e.message }
52
+ end
53
+
54
+ def search
55
+ data = @service.search(current_user)
56
+ render json: { success: true, data: serialize(data) }
57
+ end
58
+
59
+ private
60
+
61
+ def set_service
62
+ @service = DispatchService.new
63
+ end
64
+
65
+ def set_dispatch
66
+ @dispatch = Cats::Core::Dispatch.find(params[:id])
67
+ end
68
+
69
+ def model_params
70
+ params.require(:payload).permit(:reference_no, :allocation_item_id, :transporter_id, :plate_no, :driver_name,
71
+ :driver_phone, :quantity, :remark)
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,18 @@
1
+ module Cats
2
+ module Core
3
+ class ReceiptsController < ApplicationController
4
+ include Common
5
+
6
+ def index
7
+ receipts = Cats::Core::Receipt.where(dispatch_id: params[:id])
8
+ render json: { success: true, data: serialize(receipts) }
9
+ end
10
+
11
+ private
12
+
13
+ def model_params
14
+ params.require(:payload).permit(:dispatch_id, :quantity, :commodity_status, :status, :remark, :prepared_by_id)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,55 @@
1
+ module Cats
2
+ module Core
3
+ module Common
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ before_action :set_clazz
8
+ before_action :set_object, only: %i[show update]
9
+ end
10
+
11
+ def index
12
+ data = serialize(@clazz.all)
13
+ render json: { success: true, data: data }
14
+ end
15
+
16
+ def show
17
+ render json: { success: true, data: serialize(@obj) }
18
+ end
19
+
20
+ def create
21
+ obj = @clazz.new(model_params)
22
+ if obj.save
23
+ render json: { success: true, data: serialize(obj) }, status: :created
24
+ else
25
+ render json: { success: false, error: obj.errors.full_messages[0] }, status: :unprocessable_entity
26
+ end
27
+ end
28
+
29
+ def update
30
+ if @obj.update(model_params)
31
+ render json: { success: true, data: serialize(@obj) }
32
+ else
33
+ render json: { success: false, error: @obj.errors.full_messages[0] }, status: :unprocessable_entity
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def serialize(data)
40
+ ActiveModelSerializers::SerializableResource.new(data)
41
+ end
42
+
43
+ def set_clazz
44
+ @clazz = "Cats::Core::#{controller_name.classify}".constantize
45
+ end
46
+
47
+ def set_object
48
+ @obj = @clazz.find(params[:id])
49
+ end
50
+
51
+ # This class should be overridden by respective child controllers
52
+ def model_params; end
53
+ end
54
+ end
55
+ end
@@ -13,6 +13,7 @@ module Cats
13
13
  belongs_to :prepared_by, class_name: 'Cats::Core::User'
14
14
  belongs_to :transporter
15
15
  belongs_to :allocation_item
16
+ has_many :receipts
16
17
 
17
18
  validates :reference_no, :plate_no, :driver_name, :driver_phone, :quantity, :commodity_status, presence: true
18
19
  validates :dispatch_status, presence: true, inclusion: { in: DISPATCH_STATUSES }
@@ -3,9 +3,8 @@ module Cats
3
3
  class Receipt < ApplicationRecord
4
4
  # Receipt status
5
5
  DRAFT = 'Draft'.freeze
6
- ACCEPTED = 'Accepted'.freeze
7
- DECLINED = 'Declined'.freeze
8
- RECEIPT_STATUSES = [DRAFT, ACCEPTED, DECLINED].freeze
6
+ CONFIRMED = 'Confirmed'.freeze
7
+ RECEIPT_STATUSES = [DRAFT, CONFIRMED].freeze
9
8
 
10
9
  belongs_to :dispatch
11
10
  belongs_to :prepared_by, class_name: 'Cats::Core::User'
@@ -14,6 +13,28 @@ module Cats
14
13
  validates :quantity, numericality: { greater_than: 0 }
15
14
  validates :status, inclusion: { in: RECEIPT_STATUSES }
16
15
  validates :commodity_status, inclusion: { in: Cats::Core::Commodity::COMMODITY_STATUSES }
16
+ validate :validate_dispatch_status, :validate_total_quantity
17
+
18
+ delegate(:reference_no, to: :dispatch, prefix: true)
19
+ delegate(:email, to: :prepared_by, prefix: true)
20
+
21
+ def validate_dispatch_status
22
+ return unless dispatch
23
+
24
+ return if dispatch.dispatch_status == Cats::Core::Dispatch::STARTED
25
+
26
+ errors.add(:dispatch, 'Dispatch should be in "Started" state.')
27
+ end
28
+
29
+ def validate_total_quantity
30
+ return unless dispatch && quantity
31
+
32
+ received = Cats::Core::Receipt.where(dispatch: dispatch).sum(:quantity)
33
+ remaining = dispatch.quantity - received
34
+ return if quantity <= remaining
35
+
36
+ errors.add(:quantity, "Total receipt quantity is higher than dispatch quantity (Max = #{remaining}).")
37
+ end
17
38
  end
18
39
  end
19
40
  end
@@ -2,6 +2,7 @@ module Cats
2
2
  module Core
3
3
  class Transaction < ApplicationRecord
4
4
  self.abstract_class = true
5
+ after_initialize :set_status
5
6
 
6
7
  # Transaction statuses
7
8
  DRAFT = 'Draft'.freeze
@@ -25,8 +26,16 @@ module Cats
25
26
  destination.quantity += quantity
26
27
  source.save
27
28
  destination.save
29
+ self.status = COMMITTED
30
+ save
28
31
  end
29
32
  end
33
+
34
+ def set_status
35
+ return unless new_record?
36
+
37
+ self.status = DRAFT
38
+ end
30
39
  end
31
40
  end
32
41
  end
@@ -0,0 +1,8 @@
1
+ class DispatchSerializer < ActiveModel::Serializer
2
+ attributes :id, :reference_no, :allocation_item_id, :transporter_id, :transporter_name, :plate_no, :driver_name,
3
+ :driver_phone, :quantity, :remark, :prepared_by_id, :prepared_by_email, :dispatch_status, :destination
4
+
5
+ def destination
6
+ object.allocation_item.destination.name
7
+ end
8
+ end
@@ -0,0 +1,4 @@
1
+ class DispatchTransactionSerializer < ActiveModel::Serializer
2
+ attributes :id, :source_id, :source_code, :destination_id, :destination_reference_no, :quantity, :transaction_date,
3
+ :status
4
+ end
@@ -0,0 +1,4 @@
1
+ class ReceiptSerializer < ActiveModel::Serializer
2
+ attributes :id, :dispatch_id, :dispatch_reference_no, :commodity_status, :quantity, :status, :remark,
3
+ :prepared_by_id, :prepared_by_email
4
+ end
@@ -0,0 +1,136 @@
1
+ module Cats
2
+ module Core
3
+ class DispatchService
4
+ def create_receipt_authorization(reference_no, allocation_item, quantity, remark, user)
5
+ status = allocation_item.allocation.allocation_status
6
+ raise(StandardError, 'Allocation not approved.') unless status == Cats::Core::Allocation::APPROVED
7
+
8
+ transporter = Cats::Core::Transporter.find_by(code: 'SUP-TRANS')
9
+ raise(StandardError, 'Supplier transporter does not exist.') unless transporter
10
+
11
+ store = Cats::Core::Store.find_by(code: 'SUP-STORE')
12
+ raise(StandardError, 'Supplier store does not exist.') unless store
13
+
14
+ commodity = allocation_item.allocation.commodity
15
+ stack = store.stacks.find_by(commodity: commodity)
16
+
17
+ raise(StandardError, 'Commodity not found in supplier store.') unless stack
18
+
19
+ dispatch = Cats::Core::Dispatch.create!(
20
+ reference_no: reference_no,
21
+ allocation_item: allocation_item,
22
+ transporter: transporter,
23
+ plate_no: 'Supplier plate no',
24
+ driver_name: 'Supplier driver',
25
+ driver_phone: 'Supplier driver phone',
26
+ quantity: quantity,
27
+ remark: remark,
28
+ prepared_by: user,
29
+ dispatch_status: Cats::Core::Dispatch::DRAFT
30
+ )
31
+
32
+ # Create a dispatch transaction
33
+ Cats::Core::DispatchTransaction.create!(
34
+ source: stack,
35
+ destination: dispatch,
36
+ transaction_date: Date.today,
37
+ quantity: quantity,
38
+ status: Cats::Core::Transaction::DRAFT
39
+ )
40
+ dispatch
41
+ end
42
+
43
+ def search(user)
44
+ details = user.details
45
+
46
+ unless details['stores'] || details['warehouse'] || details['hub']
47
+ raise(StandardError, 'User does not have associated location.')
48
+ end
49
+
50
+ if details['stores']
51
+ warehouse = Cats::Core::Store.find(details['stores'][0]).warehouse
52
+ hub = warehouse.parent
53
+
54
+ dispatches = Cats::Core::Dispatch.joins(:allocation_item)
55
+ .where(
56
+ allocation_item: { destination: hub },
57
+ dispatch_status: Cats::Core::Dispatch::STARTED
58
+ ).or(
59
+ Cats::Core::Dispatch.joins(:allocation_item)
60
+ .where(
61
+ allocation_item: { destination: warehouse },
62
+ dispatch_status: Cats::Core::Dispatch::STARTED
63
+ )
64
+ )
65
+ elsif details['warehouse']
66
+ warehouse = Cats::Core::Location.find(details['warehouse'])
67
+ hub = warehouse.parent
68
+ dispatches = Cats::Core::Dispatch.joins(:allocation_item)
69
+ .where(
70
+ allocation_item: { destination: hub },
71
+ dispatch_status: Cats::Core::Dispatch::STARTED
72
+ ).or(
73
+ Cats::Core::Dispatch.joins(:allocation_item)
74
+ .where(
75
+ allocation_item: { destination: warehouse },
76
+ dispatch_status: Cats::Core::Dispatch::STARTED
77
+ )
78
+ )
79
+ elsif details['hub']
80
+ hub = Cats::Core::Location.find(details['hub'])
81
+ dispatches = Cats::Core::Dispatch.joins(:allocation_item)
82
+ .where(
83
+ allocation_item: { destination: hub },
84
+ dispatch_status: Cats::Core::Dispatch::STARTED
85
+ )
86
+ end
87
+ dispatches
88
+ end
89
+
90
+ def approve(dispatch)
91
+ unless dispatch.dispatch_status == Cats::Core::Dispatch::DRAFT
92
+ raise(StandardError, 'Dispatch has to be in draft state.')
93
+ end
94
+
95
+ transactions = Cats::Core::DispatchTransaction.where(destination: dispatch).count
96
+ raise(StandardError, 'Dispatch has no transactions.') unless transactions.positive?
97
+
98
+ dispatch.dispatch_status = Cats::Core::Dispatch::APPROVED
99
+ dispatch.save
100
+ dispatch
101
+ end
102
+
103
+ def start(dispatch)
104
+ unless dispatch.dispatch_status == Cats::Core::Dispatch::APPROVED
105
+ raise(StandardError, 'Dispatch has to be approved first.')
106
+ end
107
+
108
+ dispatch.dispatch_status = Cats::Core::Dispatch::STARTED
109
+ dispatch.save
110
+ transactions = Cats::Core::DispatchTransaction.where(destination: dispatch)
111
+ transactions.each(&:commit)
112
+ dispatch
113
+ end
114
+
115
+ def confirm(dispatch)
116
+ total = dispatch.receipts.sum(:quantity)
117
+
118
+ unless total == dispatch.quantity
119
+ raise(
120
+ StandardError,
121
+ "There is an amount of #{dispatch.quantity - total} in the dispatch which is unaccounted for."
122
+ )
123
+ end
124
+
125
+ Cats::Core::Dispatch.transaction do
126
+ dispatch.dispatch_status = Cats::Core::Dispatch::RECEIVED
127
+ dispatch.receipts.each { |r| r.status = Cats::Core::Receipt::CONFIRMED }
128
+ dispatch.save
129
+ dispatch.receipts.each(&:save)
130
+ end
131
+
132
+ dispatch
133
+ end
134
+ end
135
+ end
136
+ end
@@ -8,7 +8,9 @@ module Cats
8
8
  def available_space(id, level)
9
9
  case level
10
10
  when HUB
11
- warehouse_ids = Cats::Core::Location.find(id).children.map(&:id)
11
+ hub = Cats::Core::Location.find(id)
12
+ warehouses = hub.children
13
+ warehouse_ids = warehouses.map(&:id)
12
14
  stores = Cats::Core::Store.joins(:warehouse)
13
15
  .where(
14
16
  cats_core_locations: {
@@ -19,19 +21,48 @@ module Cats
19
21
  wh_details = []
20
22
  stores.each do |key, value|
21
23
  total_space = value.inject(0) { |sum, val| sum + val.available_space }
22
- details = value.map { |val| { store_id: val.id, total_space: val.available_space } }
23
- wh_details << { warehouse_id: key, total_space: total_space, details: details }
24
+ details = value.map do |val|
25
+ { store_id: val.id, code: val.code, name: val.name, total_space: val.available_space }
26
+ end
27
+ warehouse = warehouses.find(key)
28
+ wh_details << {
29
+ warehouse_id: key,
30
+ code: warehouse.code,
31
+ name: warehouse.name,
32
+ total_space: total_space,
33
+ details: details
34
+ }
24
35
  end
25
36
  hub_space = wh_details.inject(0) { |sum, val| sum + val[:total_space] }
26
- space = { hub_id: id, total_space: hub_space, details: wh_details }
37
+ space = {
38
+ hub_id: id,
39
+ code: hub.code,
40
+ name: hub.name,
41
+ total_space: hub_space,
42
+ details: wh_details
43
+ }
27
44
  when WAREHOUSE
45
+ warehouse = Cats::Core::Location.find(id)
28
46
  stores = Cats::Core::Store.where(warehouse_id: id)
29
- details = stores.map { |store| { store_id: store.id, total_space: store.available_space } }
47
+ details = stores.map do |store|
48
+ { store_id: store.id, code: store.code, name: store.name, total_space: store.available_space }
49
+ end
30
50
  total_space = stores.inject(0) { |sum, val| sum + val.available_space }
31
- space = { warehouse_id: id, total_space: total_space, details: details }
51
+ space = {
52
+ warehouse_id: id,
53
+ code: warehouse.code,
54
+ name: warehouse.name,
55
+ total_space: total_space,
56
+ details: details
57
+ }
32
58
  when STORE
33
59
  store = Cats::Core::Store.find(id)
34
- space = { store_id: store.id, total_space: store.available_space }
60
+ space = {
61
+ store_id: store.id,
62
+ code: store.code,
63
+ name: store.name,
64
+ total_space: store.available_space
65
+ }
35
66
  end
36
67
  space
37
68
  end
data/config/routes.rb CHANGED
@@ -33,11 +33,11 @@ Cats::Core::Engine.routes.draw do
33
33
  to: 'spaces#available_space',
34
34
  as: :hub_space,
35
35
  defaults: { level: Cats::Core::SpaceService::HUB }
36
- get '/warehousess/:id/space',
36
+ get '/warehouses/:id/space',
37
37
  to: 'spaces#available_space',
38
38
  as: :warehouse_space,
39
39
  defaults: { level: Cats::Core::SpaceService::WAREHOUSE }
40
- get '/store/:id/space',
40
+ get '/stores/:id/space',
41
41
  to: 'spaces#available_space',
42
42
  as: :store_space,
43
43
  defaults: { level: Cats::Core::SpaceService::STORE }
@@ -49,4 +49,22 @@ Cats::Core::Engine.routes.draw do
49
49
  end
50
50
  resources :currencies, except: %i[destroy]
51
51
  resources :unit_of_measures, except: %i[destroy]
52
+
53
+ get '/allocation_items/:id/dispatches', controller: :dispatches, action: :index, as: :dispatches_allocation_item
54
+ post '/allocation_items/:id/receipt_authorizaton',
55
+ controller: :dispatches,
56
+ action: :create_receipt_authorization,
57
+ as: :receipt_authorization_allocation_item
58
+
59
+ get '/dispatches/search', controller: :dispatches, action: :search, as: :search_dispatches
60
+ resources :dispatches, except: %i[index destroy] do
61
+ member do
62
+ get 'receipts', controller: :receipts, action: :index
63
+ post 'approve'
64
+ post 'start'
65
+ post 'confirm'
66
+ end
67
+ end
68
+ resources :dispatch_transactions, except: %i[new edit destroy]
69
+ resources :receipts, except: %i[index new edit destroy]
52
70
  end
@@ -1,5 +1,5 @@
1
1
  module Cats
2
2
  module Core
3
- VERSION = '1.0.48'.freeze
3
+ VERSION = '1.1.1'.freeze
4
4
  end
5
5
  end
@@ -1,6 +1,6 @@
1
1
  FactoryBot.define do
2
2
  factory :receipt, class: 'Cats::Core::Receipt' do
3
- dispatch
3
+ dispatch factory: :dispatch, dispatch_status: Cats::Core::Dispatch::STARTED
4
4
  quantity { 1.5 }
5
5
  commodity_status { Cats::Core::Commodity::GOOD }
6
6
  status { Cats::Core::Receipt::DRAFT }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cats_core
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.48
4
+ version: 1.1.1
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-09-22 00:00:00.000000000 Z
11
+ date: 2021-09-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: active_model_serializers
@@ -220,13 +220,17 @@ files:
220
220
  - app/controllers/cats/core/application_controller.rb
221
221
  - app/controllers/cats/core/commodity_categories_controller.rb
222
222
  - app/controllers/cats/core/currencies_controller.rb
223
+ - app/controllers/cats/core/dispatch_transactions_controller.rb
224
+ - app/controllers/cats/core/dispatches_controller.rb
223
225
  - app/controllers/cats/core/locations_controller.rb
224
226
  - app/controllers/cats/core/menus_controller.rb
225
227
  - app/controllers/cats/core/notifications_controller.rb
228
+ - app/controllers/cats/core/receipts_controller.rb
226
229
  - app/controllers/cats/core/roles_controller.rb
227
230
  - app/controllers/cats/core/spaces_controller.rb
228
231
  - app/controllers/cats/core/unit_of_measures_controller.rb
229
232
  - app/controllers/cats/core/users_controller.rb
233
+ - app/controllers/concerns/cats/core/common.rb
230
234
  - app/jobs/cats/core/application_job.rb
231
235
  - app/models/cats/core/allocation.rb
232
236
  - app/models/cats/core/allocation_item.rb
@@ -269,9 +273,13 @@ files:
269
273
  - app/notifications/cats/core/simple_notification.rb
270
274
  - app/serializers/cats/core/commodity_category_serializer.rb
271
275
  - app/serializers/cats/core/currency_serializer.rb
276
+ - app/serializers/cats/core/dispatch_serializer.rb
277
+ - app/serializers/cats/core/dispatch_transaction_serializer.rb
272
278
  - app/serializers/cats/core/location_serializer.rb
279
+ - app/serializers/cats/core/receipt_serializer.rb
273
280
  - app/serializers/cats/core/role_menu_serializer.rb
274
281
  - app/serializers/cats/core/unit_of_measure_serializer.rb
282
+ - app/services/cats/core/dispatch_service.rb
275
283
  - app/services/cats/core/menu_service.rb
276
284
  - app/services/cats/core/notification_service.rb
277
285
  - app/services/cats/core/space_service.rb