dscf-marketplace 0.11.1 → 0.12.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: d7c97626ff0e52c8c5d5e601c4b33e5dbdd941d708096d20c5cdd5cf583b87eb
4
- data.tar.gz: e9f22a25e8416efda6cb91e71d9073f69476753ee3cf049f37f5c96e3acdff57
3
+ metadata.gz: b6818b94cd7c2f6c55d5e39da13eee317dbfa3879e17d442be6821a50f993659
4
+ data.tar.gz: 840863a3f380843728489b4b6e6518fa0d5bed4cb73a5dd3bddde05f8c525c56
5
5
  SHA512:
6
- metadata.gz: de2a962d28ad6ecb494784b63392adc834de9efda0a785c233a678f2dfdb1025c2b63993ccb5504cf1a6c632026afe6d0a341f4dd30ae1cd69c899f35d29b236
7
- data.tar.gz: ca39c0da64f9c4387022bf67dbf9a857a0e3cd8fc47fd257a3dba6b968b1ca02b53bd5d7f304b8a1de2e4f2b2edaf945484db7bbc3bfade0279fe4545c55b5c0
6
+ metadata.gz: efa76d0e7f656a6af4de6a2fce5923992872da28135c6a8c723f35717926cd1326aad14a10a5a8ce5ce5961f369f0872fb7d4ef3bdfaff3dc1f83c1b90c5177f
7
+ data.tar.gz: d0f773b3303836c0dec8d963bc5397a93846d26a0ba83adc9625800a1c4c2239afec6e8fc25cdb15e9dfa2c8fcca6152ca4e02d7810a66ebc5b5c5af531e6420
@@ -120,21 +120,33 @@ module Dscf
120
120
  authorize obj, :resolve_item?
121
121
 
122
122
  item = obj.order_items.find(params[:order_item_id])
123
- # Example: for price_changed, accept optional resolved_unit_price from params
124
- if params[:resolved_unit_price].present?
125
- item.resolved_unit_price = params[:resolved_unit_price]
126
- item.validation_status = :validated
127
- end
128
- if params[:resolved_quantity].present?
129
- item.resolved_quantity = params[:resolved_quantity]
130
- item.validation_status = :validated
131
- end
123
+
132
124
  if params[:action_type] == "remove"
125
+ # No quantity available (or the aggregator otherwise can't source it):
126
+ # cancel the line rather than zeroing quantity (which fails the
127
+ # quantity > 0 validation) — cancelled lines are excluded from the
128
+ # order total and from all_items_validated? gating.
133
129
  item.validation_status = :validated
134
- item.quantity = 0 # will be cleaned or marked
130
+ item.status = :cancelled
131
+ else
132
+ # Store the resolved_* audit trail AND apply it to the actual
133
+ # unit_price/quantity — those are what total_amount/subtotal read,
134
+ # so "use listed price" / "reduce to available qty" must land here
135
+ # to actually change what's charged.
136
+ if params[:resolved_unit_price].present?
137
+ item.resolved_unit_price = params[:resolved_unit_price]
138
+ item.unit_price = params[:resolved_unit_price]
139
+ item.validation_status = :validated
140
+ end
141
+ if params[:resolved_quantity].present?
142
+ item.resolved_quantity = params[:resolved_quantity]
143
+ item.quantity = params[:resolved_quantity]
144
+ item.validation_status = :validated
145
+ end
135
146
  end
136
147
 
137
148
  item.save!
149
+ obj.save! # recompute total_amount from the resolved/cancelled line
138
150
  create_notification_for_order(obj, :item_resolved)
139
151
  render_success(data: item)
140
152
  rescue => e
@@ -235,6 +247,19 @@ module Dscf
235
247
  render_error(errors: e.message, status: :unprocessable_entity)
236
248
  end
237
249
 
250
+ # Re-source loop: reopen a cancelled line (e.g. a supplier who didn't
251
+ # confirm) so the aggregator can assign a new source via assign_source.
252
+ def reopen_item
253
+ obj = find_record
254
+ authorize obj, :split?
255
+
256
+ item = obj.order_items.find(params[:order_item_id])
257
+ Dscf::Marketplace::OrderSplittingService.reopen_item(obj, item)
258
+ render_success(data: obj.reload, serializer_options: { include: default_serializer_includes[:show] || [] })
259
+ rescue => e
260
+ render_error(errors: e.message, status: :unprocessable_entity)
261
+ end
262
+
238
263
  def retailer_confirm
239
264
  obj = find_record
240
265
  authorize obj, :confirm? # reuse existing or add specific
@@ -122,13 +122,21 @@ module Dscf::Marketplace
122
122
  def total_amount
123
123
  # Return stored value if it exists and is greater than 0, otherwise calculate
124
124
  stored_value = super
125
- calculated_value = order_items.sum { |item| item.quantity * item.unit_price }
125
+ calculated_value = billable_order_items.sum { |item| item.quantity * item.unit_price }
126
126
 
127
127
  # Return stored value if it's set and valid, otherwise return calculated value
128
128
  return stored_value if stored_value.present? && stored_value > 0
129
129
  calculated_value
130
130
  end
131
131
 
132
+ # Lines that should count toward the order total / retailer-facing amount.
133
+ # A cancelled line (removed during validation, or a rejected supplier
134
+ # allocation) is not being fulfilled, so it shouldn't inflate what the
135
+ # retailer is asked to confirm and pay.
136
+ def billable_order_items
137
+ order_items.reject(&:cancelled?)
138
+ end
139
+
132
140
  def supplier
133
141
  if rfq_based?
134
142
  quotation&.business
@@ -170,16 +178,22 @@ module Dscf::Marketplace
170
178
  end
171
179
 
172
180
  def calculate_total_amount
173
- self.total_amount = order_items.sum { |item| item.quantity * item.unit_price }
181
+ self.total_amount = billable_order_items.sum { |item| item.quantity * item.unit_price }
174
182
  end
175
183
 
176
- # Workflow helpers for Sprint 2 order validation + splitting
177
- def validating?
178
- pending? || status.to_s == "processing"
184
+ # Workflow helpers for Sprint 2 order validation + splitting.
185
+ # Note: `validating?` is the enum-generated predicate (true only when
186
+ # status == "validating"). Use this for "can still be run through
187
+ # validation" — covers the actual validating status plus the states an
188
+ # order can be created in (pending / legacy processing) before it does.
189
+ def awaiting_validation?
190
+ pending? || validating? || status.to_s == "processing"
179
191
  end
180
192
 
181
193
  def all_items_validated?
182
- order_items.all? { |i| i.validation_status == "validated" }
194
+ # A cancelled line (removed during validation) is no longer part of what
195
+ # needs to ship, so it shouldn't block Split from becoming available.
196
+ order_items.reject(&:cancelled?).all? { |i| i.validation_status == "validated" }
183
197
  end
184
198
 
185
199
  def validation_summary
@@ -203,8 +217,12 @@ module Dscf::Marketplace
203
217
  end
204
218
 
205
219
  def supplier_confirmation_complete?
206
- # simplistic: all items have source and are confirmed or cancelled
207
- order_items.all? { |i| i.source_id.present? && (i.status.to_s == "confirmed" || i.status.to_s == "cancelled") }
220
+ # A cancelled line (removed at validation time, before a source was ever
221
+ # assigned, or rejected during confirmation) is done responding either
222
+ # way — only a still-live line needs a source + confirmed status.
223
+ order_items.all? do |i|
224
+ i.cancelled? || (i.source_id.present? && i.status.to_s == "confirmed")
225
+ end
208
226
  end
209
227
 
210
228
  private
@@ -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
@@ -99,6 +100,29 @@ module Dscf
99
100
  order.mark_waiting_retailer_confirmation! if order.supplier_confirmation_complete?
100
101
  order_item
101
102
  end
103
+
104
+ # Re-source loop (requirements doc: when a supplier doesn't confirm, "search
105
+ # for other sources" then go back to change-product-source). Reopens a
106
+ # cancelled line for a fresh source assignment: clears its source and any
107
+ # stale adjustment proposal, and puts it back in :processing. If the order
108
+ # had already advanced (because every other line had responded), pull it
109
+ # back to waiting_supplier_confirmation so the reassigned line gets a
110
+ # chance to be confirmed before the retailer sees the order.
111
+ def self.reopen_item(order, order_item)
112
+ raise "Only a cancelled item can be reopened" unless order_item.cancelled?
113
+
114
+ order_item.status = :processing
115
+ order_item.source_type = nil
116
+ order_item.source_id = nil
117
+ order_item.supplier_adjusted_unit_price = nil
118
+ order_item.supplier_adjusted_quantity = nil
119
+ order_item.supplier_adjustment_note = nil
120
+ order_item.save!
121
+
122
+ order.reload
123
+ order.mark_waiting_supplier_confirmation! unless order.waiting_supplier_confirmation?
124
+ order_item
125
+ end
102
126
  end
103
127
  end
104
128
  end
@@ -6,12 +6,14 @@ module Dscf
6
6
  # validated, no_longer_listed, price_changed, low_quantity
7
7
  # Also populates resolved_* fields where applicable.
8
8
  def self.validate(order)
9
- order.order_items.each do |item|
9
+ # A cancelled line (removed earlier, or a rejected allocation) is done —
10
+ # re-validating it could resurrect a stale validation_status/note.
11
+ order.order_items.reject(&:cancelled?).each do |item|
10
12
  validate_item(item)
11
13
  end
12
- # Transition order to splitting ready if all validated
13
- if order.order_items.all? { |i| i.validation_status == "validated" }
14
- order.update!(status: :splitting) if order.validating?
14
+ # Transition order to splitting ready if all (non-cancelled) items validated
15
+ if order.all_items_validated? && order.awaiting_validation?
16
+ order.update!(status: :splitting)
15
17
  end
16
18
  order.reload
17
19
  end
data/config/routes.rb CHANGED
@@ -135,6 +135,7 @@ Dscf::Marketplace::Engine.routes.draw do
135
135
  post "adjust_item"
136
136
  post "accept_adjustment"
137
137
  post "reject_adjustment"
138
+ post "reopen_item"
138
139
  post "retailer_confirm"
139
140
  end
140
141
  collection do
data/db/demo_seeds.rb CHANGED
@@ -115,11 +115,14 @@ Dscf::Marketplace::Agent.create!(name: "Yonas Girma", phone: "+251912000003", se
115
115
  Dscf::Marketplace::Agent.create!(name: "Aster Bekele", phone: "+251912000004", service_area: "Megenagna", status: :inactive, verification_status: :verified, onboarded_by: biz_agg)
116
116
  puts " Agents: 4 (2 pending KYC, 1 inactive)"
117
117
 
118
- # ── Aggregator Listings ──
119
- Dscf::Marketplace::AggregatorListing.create!(aggregator: biz_agg, supplier_product: sp1, price: 950, quantity: 100, status: 0)
120
- Dscf::Marketplace::AggregatorListing.create!(aggregator: biz_agg, supplier_product: sp4, price: 320, quantity: 50, status: 0)
121
- Dscf::Marketplace::AggregatorListing.create!(aggregator: biz_agg, supplier_product: sp6b, price: 420, quantity: 80, status: 0)
122
- puts " Aggregator Listings: 3"
118
+ # ── Aggregator Listings (source_kind required since 0.10.0) ──
119
+ # Supplier-sourced (resell a supplier_product):
120
+ Dscf::Marketplace::AggregatorListing.create!(aggregator: biz_agg, source_kind: :supplier, supplier_product: sp1, price: 950, quantity: 100, status: 0)
121
+ Dscf::Marketplace::AggregatorListing.create!(aggregator: biz_agg, source_kind: :supplier, supplier_product: sp4, price: 320, quantity: 50, status: 0)
122
+ Dscf::Marketplace::AggregatorListing.create!(aggregator: biz_agg, source_kind: :supplier, supplier_product: sp6b, price: 420, quantity: 80, status: 0)
123
+ # Catalog-sourced (aggregator lists a master-catalog product directly, no upstream cost):
124
+ Dscf::Marketplace::AggregatorListing.create!(aggregator: biz_agg, source_kind: :catalog, product: p2, price: 700, quantity: 60, status: 0)
125
+ puts " Aggregator Listings: 4 (3 supplier, 1 catalog)"
123
126
 
124
127
  # ── Demo Address ──
125
128
  address = Dscf::Core::Address.first || Dscf::Core::Address.create!(addressable: admin, address_line_1: "Bole Road, Building 12", city: "Addis Ababa", country: "Ethiopia")
@@ -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 confirm_item adjust_item accept_adjustment reject_adjustment 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 reopen_item 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.11.1".freeze
3
+ VERSION = "0.12.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.11.1
4
+ version: 0.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Asrat