cats_core 1.0.50 → 1.1.3

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: b13a3549ac212c8e869ce5aff6b3508787e75af7afaeadba4b0e99f5476b101e
4
- data.tar.gz: d4993f0e36936fc4ed18ae9accca173189056c3cddae7fc2f16704f6509bac80
3
+ metadata.gz: 20f6349c8a17610b338d05e2fa64a35d76bf0f3737f898ed9ab4d58d76053085
4
+ data.tar.gz: 74f16a28eaaf4608b0b2d1935331d8837f49cc805511412df57f902c6d421349
5
5
  SHA512:
6
- metadata.gz: 91d4ba5e59ef7da90cf36d6073d20819496bae612eff73cdeccada481293a956311732201b049da61e84ba1c12b1a7464d636202b101f66a49bcf22146211440
7
- data.tar.gz: afdceef95b3321eaea7ebad43bd622b814ef268f97b20c5815af07376fc710b338b74a25bc3e81d6c4b366f00e30e6553c2f2dcc38e84685bc670201ce4e4e13
6
+ metadata.gz: 1b3dbbd8c4da02cb692bf9eae19d6e653ced98de74f54a6a9cae9c6c0dddbe2dfd7fb7b5ec4d1881445e0cf159a2e093111b2acc25dd72309b2c183ff6f94c5b
7
+ data.tar.gz: 05ad4779c0d90d975fc172c4c98c6729e3ff45092953408c31f4cdade4b01dae94eaf655dc857d793e6064770a3a44b93127e62b31ee890633871a509998768a
@@ -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, params[:status])
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,10 @@ 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
+ STACKING = 'Stacking'.freeze
8
+ STACKED = 'Stacked'.freeze
9
+ RECEIPT_STATUSES = [DRAFT, CONFIRMED, STACKING, STACKED].freeze
9
10
 
10
11
  belongs_to :dispatch
11
12
  belongs_to :prepared_by, class_name: 'Cats::Core::User'
@@ -14,6 +15,35 @@ module Cats
14
15
  validates :quantity, numericality: { greater_than: 0 }
15
16
  validates :status, inclusion: { in: RECEIPT_STATUSES }
16
17
  validates :commodity_status, inclusion: { in: Cats::Core::Commodity::COMMODITY_STATUSES }
18
+ validate :validate_dispatch_status, :validate_total_quantity
19
+
20
+ delegate(:reference_no, to: :dispatch, prefix: true)
21
+ delegate(:email, to: :prepared_by, prefix: true)
22
+
23
+ def validate_dispatch_status
24
+ return unless dispatch
25
+
26
+ statuses = [Cats::Core::Dispatch::STARTED, Cats::Core::Dispatch::RECEIVED]
27
+ return if statuses.include?(dispatch.dispatch_status)
28
+
29
+ errors.add(:dispatch, 'should be in "Started" state.')
30
+ end
31
+
32
+ def validate_total_quantity
33
+ return unless dispatch && quantity
34
+
35
+ received = dispatch.receipts.sum(:quantity)
36
+ diff = dispatch.quantity - received
37
+ if new_record?
38
+ return if quantity <= diff
39
+
40
+ errors.add(:quantity, "total is higher than dispatch quantity (Max = #{diff}).")
41
+ else
42
+ return unless diff.negative?
43
+
44
+ errors.add(:quantity, "total is higher than dispatch quantity (Max = #{diff.abs}).")
45
+ end
46
+ end
17
47
  end
18
48
  end
19
49
  end
@@ -3,6 +3,8 @@ module Cats
3
3
  class ReceiptTransaction < Transaction
4
4
  belongs_to :source, class_name: 'Cats::Core::Receipt'
5
5
  belongs_to :destination, class_name: 'Cats::Core::Stack'
6
+
7
+ delegate(:code, to: :destination, prefix: true)
6
8
  end
7
9
  end
8
10
  end
@@ -12,12 +12,14 @@ module Cats
12
12
  validates :transaction_date, :quantity, :status, presence: true
13
13
  validates :quantity, numericality: { greater_than: 0 }
14
14
  validates :status, inclusion: { in: STATUSES }
15
- validate :validate_quantity
15
+ validate :validate_quantity, unless: :skip_quantity_validation
16
16
 
17
17
  def validate_quantity
18
18
  return unless quantity.present? && source.present?
19
19
 
20
- errors.add(:quantity, 'cannot be more than source quantity') if quantity > source.quantity
20
+ total = self.class.where(source: source).sum(:quantity)
21
+ diff = source.quantity - total
22
+ errors.add(:quantity, "total is higher than source quantity (Max = #{diff}).") if quantity > diff
21
23
  end
22
24
 
23
25
  def commit
@@ -27,10 +29,23 @@ module Cats
27
29
  source.save
28
30
  destination.save
29
31
  self.status = COMMITTED
30
- save
32
+ save!
31
33
  end
32
34
  end
33
35
 
36
+ def skip_quantity_validation
37
+ # Quantity validation should be skipped if we are commiting transactions.
38
+ (
39
+ instance_of?(Cats::Core::DispatchTransaction) &&
40
+ destination &&
41
+ destination.dispatch_status == Cats::Core::Dispatch::STARTED
42
+ ) || (
43
+ instance_of?(Cats::Core::ReceiptTransaction) &&
44
+ source &&
45
+ source.status == Cats::Core::Receipt::STACKING
46
+ )
47
+ end
48
+
34
49
  def set_status
35
50
  return unless new_record?
36
51
 
@@ -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, status)
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: status
58
+ ).or(
59
+ Cats::Core::Dispatch.joins(:allocation_item)
60
+ .where(
61
+ allocation_item: { destination: warehouse },
62
+ dispatch_status: status
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: status
72
+ ).or(
73
+ Cats::Core::Dispatch.joins(:allocation_item)
74
+ .where(
75
+ allocation_item: { destination: warehouse },
76
+ dispatch_status: status
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: status
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
data/config/routes.rb CHANGED
@@ -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.50'.freeze
3
+ VERSION = '1.1.3'.freeze
4
4
  end
5
5
  end
@@ -1,5 +1,5 @@
1
1
  FactoryBot.define do
2
- factory :receipt_transaction do
2
+ factory :receipt_transaction, class: 'Cats::Core::ReceiptTransaction' do
3
3
  source factory: :receipt
4
4
  destination factory: :stack
5
5
  transaction_date { Date.today }
@@ -1,7 +1,7 @@
1
1
  FactoryBot.define do
2
2
  factory :receipt, class: 'Cats::Core::Receipt' do
3
- dispatch
4
- quantity { 1.5 }
3
+ dispatch factory: :dispatch, dispatch_status: Cats::Core::Dispatch::STARTED
4
+ quantity { 50 }
5
5
  commodity_status { Cats::Core::Commodity::GOOD }
6
6
  status { Cats::Core::Receipt::DRAFT }
7
7
  remark { FFaker::Name.name }
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.50
4
+ version: 1.1.3
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-23 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