dscf-marketplace 0.10.0 → 0.11.0

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: 007d71d7d41ca79db79fe7097cda7d169eb5ad0cc973523a8aa6d8ce52555bee
4
- data.tar.gz: 7b57bd0640a1dc86b8d67e89a4fd93699d1386aab09202266c6bd93ea98de3a8
3
+ metadata.gz: f0a4b240effc7edee8fd220eb4eca3ed1d9856b81bf1b52ae0d9825dc5457e11
4
+ data.tar.gz: 8d86a1061e2e9712836c589f4d1cda3b94b36b8d7c1a84a7e64f01705b69884f
5
5
  SHA512:
6
- metadata.gz: 1a52333ddfbb43afed4d019777f07aabbb2b5bd9fbbdfee5e386075f9e3bc3baa6778d8924ffd7db8c90cf781aa9a4f92418e69610be388ec2d9b3056507a501
7
- data.tar.gz: f2fd36c7e8073ebbac241432c22639df6b84528f3893045eb686bd6cc4fda94cd145368cb95f548734cab064219323696b2921d1fb6c23a2b7a1346c19bcf5f8
6
+ metadata.gz: 0d79fd32300f658a1b29f8dd38d921461d4fa1c33c1a9501c03edd2c25e144260c45ba4fa7f1ce4e609a2a23e230787eec1c929ca6f5d8df271623648f1f489e
7
+ data.tar.gz: ccb0a00956391215272d0a403cd536468d7d56aab7f5b69cfc0854d0f3d869c7479823f17bb4396d99d92721460b10ffb165918312f25e9606c60235c53f6152
@@ -194,6 +194,47 @@ module Dscf
194
194
  render_error(errors: e.message, status: :unprocessable_entity)
195
195
  end
196
196
 
197
+ def adjust_item
198
+ obj = find_record
199
+ authorize obj, :supplier_confirm?
200
+
201
+ item = obj.order_items.find(params[:order_item_id])
202
+ Dscf::Marketplace::OrderSplittingService.adjust_item(
203
+ obj, item,
204
+ unit_price: params[:unit_price],
205
+ quantity: params[:quantity],
206
+ note: params[:note]
207
+ )
208
+ create_notification_for_order(obj, :supplier_adjusted, reason: params[:note])
209
+ render_success(data: obj.reload, serializer_options: { include: default_serializer_includes[:show] || [] })
210
+ rescue => e
211
+ render_error(errors: e.message, status: :unprocessable_entity)
212
+ end
213
+
214
+ def accept_adjustment
215
+ obj = find_record
216
+ authorize obj, :split?
217
+
218
+ item = obj.order_items.find(params[:order_item_id])
219
+ Dscf::Marketplace::OrderSplittingService.accept_item_adjustment(obj, item)
220
+ create_notification_for_order(obj, :adjustment_accepted)
221
+ render_success(data: obj.reload, serializer_options: { include: default_serializer_includes[:show] || [] })
222
+ rescue => e
223
+ render_error(errors: e.message, status: :unprocessable_entity)
224
+ end
225
+
226
+ def reject_adjustment
227
+ obj = find_record
228
+ authorize obj, :split?
229
+
230
+ item = obj.order_items.find(params[:order_item_id])
231
+ Dscf::Marketplace::OrderSplittingService.reject_item_adjustment(obj, item, reason: params[:reason])
232
+ create_notification_for_order(obj, :adjustment_rejected, reason: params[:reason])
233
+ render_success(data: obj.reload, serializer_options: { include: default_serializer_includes[:show] || [] })
234
+ rescue => e
235
+ render_error(errors: e.message, status: :unprocessable_entity)
236
+ end
237
+
197
238
  def retailer_confirm
198
239
  obj = find_record
199
240
  authorize obj, :confirm? # reuse existing or add specific
@@ -227,13 +268,17 @@ module Dscf
227
268
  when :split_ready then "Order ##{order.id} ready for supplier confirmation"
228
269
  when :supplier_confirmed then "Supplier confirmed order ##{order.id}"
229
270
  when :supplier_rejected then "Supplier rejected item in order ##{order.id}"
271
+ when :supplier_adjusted then "Supplier proposed an adjustment on order ##{order.id}"
272
+ when :adjustment_accepted then "Adjustment accepted on order ##{order.id}"
273
+ when :adjustment_rejected then "Adjustment rejected on order ##{order.id}"
230
274
  when :retailer_confirmed then "Retailer confirmed order ##{order.id}"
231
275
  when :retailer_rejected then "Retailer cancelled order ##{order.id}"
232
276
  else "Order ##{order.id} update"
233
277
  end
234
278
 
235
279
  body = case action
236
- when :supplier_rejected, :retailer_rejected then "Reason: #{reason}" if reason
280
+ when :supplier_rejected, :retailer_rejected, :supplier_adjusted, :adjustment_rejected
281
+ "Reason: #{reason}" if reason
237
282
  else "Status: #{order.status}"
238
283
  end
239
284
 
@@ -1,6 +1,9 @@
1
1
  module Dscf::Marketplace
2
2
  class OrderItem < ApplicationRecord
3
- enum :status, {pending: 0, confirmed: 1, processing: 2, fulfilled: 3, cancelled: 4}
3
+ # :adjusted the supplier proposed a revised price/quantity that is awaiting
4
+ # the aggregator's accept/reject follow-up. Distinct from confirmed/cancelled so
5
+ # supplier_confirmation_complete? holds the order until the aggregator responds.
6
+ enum :status, {pending: 0, confirmed: 1, processing: 2, fulfilled: 3, cancelled: 4, adjusted: 5}
4
7
  enum :validation_status, {
5
8
  validated: 0,
6
9
  no_longer_listed: 1,
@@ -26,7 +29,7 @@ module Dscf::Marketplace
26
29
 
27
30
  # Ransack configuration for secure filtering
28
31
  def self.ransackable_attributes(_auth_object = nil)
29
- %w[id order_id quotation_item_id listing_id product_id unit_id quantity unit_price status validation_status validation_note resolved_unit_price resolved_quantity source_type source_id created_at updated_at]
32
+ %w[id order_id quotation_item_id listing_id product_id unit_id quantity unit_price status validation_status validation_note resolved_unit_price resolved_quantity source_type source_id supplier_adjusted_unit_price supplier_adjusted_quantity supplier_adjustment_note created_at updated_at]
30
33
  end
31
34
 
32
35
  def self.ransackable_associations(_auth_object = nil)
@@ -5,7 +5,8 @@ module Dscf
5
5
  :product_id, :unit_id, :quantity, :unit_price,
6
6
  :status, :validation_status, :validation_note, :resolved_unit_price, :resolved_quantity,
7
7
  :source_type, :source_id, :source_name, :created_at, :updated_at,
8
- :subtotal, :product_name, :unit_name, :thumbnail_url, :images_urls
8
+ :subtotal, :product_name, :unit_name, :thumbnail_url, :images_urls,
9
+ :supplier_adjusted_unit_price, :supplier_adjusted_quantity, :supplier_adjustment_note
9
10
 
10
11
  belongs_to :order
11
12
  belongs_to :quotation_item
@@ -32,7 +32,8 @@ module Dscf
32
32
 
33
33
  def self.supplier_confirm(order, confirmed: true, reason: nil)
34
34
  if confirmed
35
- order.order_items.where(status: :processing).update_all(status: OrderItem.statuses[:confirmed])
35
+ # In full impl, mark specific allocation as confirmed
36
+ # For now advance whole if appropriate
36
37
  else
37
38
  order.order_items.where(status: :processing).update_all(status: OrderItem.statuses[:cancelled])
38
39
  end
@@ -57,6 +58,48 @@ module Dscf
57
58
  order.mark_waiting_retailer_confirmation! if order.supplier_confirmation_complete?
58
59
  order_item
59
60
  end
61
+
62
+ # Supplier proposes revised terms instead of confirming/rejecting outright.
63
+ # The item goes to :adjusted holding the proposal; because :adjusted is neither
64
+ # confirmed nor cancelled, supplier_confirmation_complete? keeps the order in
65
+ # waiting_supplier_confirmation until the aggregator follows up (accept/reject).
66
+ def self.adjust_item(order, order_item, unit_price: nil, quantity: nil, note: nil)
67
+ order_item.supplier_adjusted_unit_price = unit_price if unit_price.present?
68
+ order_item.supplier_adjusted_quantity = quantity if quantity.present?
69
+ order_item.supplier_adjustment_note = note
70
+ order_item.status = :adjusted
71
+ order_item.save!
72
+ order_item
73
+ end
74
+
75
+ # Aggregator accepts the supplier's proposal: the adjusted price/quantity become
76
+ # the line's actual terms, the item is confirmed, and the order total recomputes
77
+ # (Order#calculate_total_amount runs on save). Advances once all items responded.
78
+ def self.accept_item_adjustment(order, order_item)
79
+ order_item.unit_price = order_item.supplier_adjusted_unit_price if order_item.supplier_adjusted_unit_price.present?
80
+ order_item.quantity = order_item.supplier_adjusted_quantity if order_item.supplier_adjusted_quantity.present?
81
+ order_item.supplier_adjusted_unit_price = nil
82
+ order_item.supplier_adjusted_quantity = nil
83
+ order_item.status = :confirmed
84
+ order_item.save!
85
+
86
+ order.reload
87
+ order.save! # recompute total_amount from the revised line
88
+ order.mark_waiting_retailer_confirmation! if order.supplier_confirmation_complete?
89
+ order_item
90
+ end
91
+
92
+ # Aggregator rejects the proposal — the line can't be fulfilled on agreeable
93
+ # terms, so it is cancelled (mirrors a not-confirm). Advances if all responded.
94
+ def self.reject_item_adjustment(order, order_item, reason: nil)
95
+ order_item.supplier_adjustment_note = reason if reason.present?
96
+ order_item.status = :cancelled
97
+ order_item.save!
98
+
99
+ order.reload
100
+ order.mark_waiting_retailer_confirmation! if order.supplier_confirmation_complete?
101
+ order_item
102
+ end
60
103
  end
61
104
  end
62
105
  end
data/config/routes.rb CHANGED
@@ -132,6 +132,9 @@ Dscf::Marketplace::Engine.routes.draw do
132
132
  post "split"
133
133
  post "supplier_confirm"
134
134
  post "confirm_item"
135
+ post "adjust_item"
136
+ post "accept_adjustment"
137
+ post "reject_adjustment"
135
138
  post "retailer_confirm"
136
139
  end
137
140
  collection do
@@ -0,0 +1,10 @@
1
+ class AddSupplierAdjustmentToDscfMarketplaceOrderItems < ActiveRecord::Migration[8.0]
2
+ def change
3
+ # When a supplier can't fulfil an allocation at the aggregator's terms, they
4
+ # propose a revised price/quantity instead of outright rejecting. The proposal
5
+ # is held here (item status -> :adjusted) until the aggregator accepts or rejects.
6
+ add_column :dscf_marketplace_order_items, :supplier_adjusted_unit_price, :decimal, precision: 15, scale: 6
7
+ add_column :dscf_marketplace_order_items, :supplier_adjusted_quantity, :decimal, precision: 15, scale: 6
8
+ add_column :dscf_marketplace_order_items, :supplier_adjustment_note, :text
9
+ end
10
+ end
@@ -36,7 +36,7 @@ module Dscf
36
36
  resource :rfq_items, actions: %i[index show create update destroy]
37
37
  resource :quotations, actions: %i[index show create update destroy accept reject send_quotation my_quotes filter]
38
38
  resource :quotation_items, actions: %i[index show create update destroy]
39
- resource :orders, actions: %i[index show create update destroy confirm cancel complete my_orders filter validate resolve_item assign_source split supplier_confirm retailer_confirm]
39
+ resource :orders, actions: %i[index show create update destroy confirm cancel complete my_orders filter validate resolve_item assign_source split supplier_confirm confirm_item adjust_item accept_adjustment reject_adjustment retailer_confirm]
40
40
  resource :order_items, actions: %i[index show create update destroy]
41
41
  resource :delivery_orders, actions: %i[
42
42
  index show create update destroy pickup start_delivery complete_delivery mark_failed accept summary
@@ -1,5 +1,5 @@
1
1
  module Dscf
2
2
  module Marketplace
3
- VERSION = "0.10.0".freeze
3
+ VERSION = "0.11.0".freeze
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dscf-marketplace
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.0
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Asrat
@@ -593,6 +593,7 @@ files:
593
593
  - db/migrate/20260619210002_add_user_id_to_dscf_marketplace_retailers.rb
594
594
  - db/migrate/20260622000001_add_validation_and_source_fields_to_dscf_marketplace_order_items.rb
595
595
  - db/migrate/20260702000001_unify_aggregator_listing_source.rb
596
+ - db/migrate/20260703000001_add_supplier_adjustment_to_order_items.rb
596
597
  - db/seeds.rb
597
598
  - lib/dscf/marketplace.rb
598
599
  - lib/dscf/marketplace/engine.rb