cats_core 1.0.50 → 1.1.3

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