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 +4 -4
- data/app/controllers/dscf/marketplace/orders_controller.rb +35 -10
- data/app/models/dscf/marketplace/order.rb +26 -8
- data/app/services/dscf/marketplace/order_splitting_service.rb +25 -1
- data/app/services/dscf/marketplace/order_validation_service.rb +6 -4
- data/config/routes.rb +1 -0
- data/db/demo_seeds.rb +8 -5
- data/lib/dscf/marketplace/engine.rb +1 -1
- data/lib/dscf/marketplace/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b6818b94cd7c2f6c55d5e39da13eee317dbfa3879e17d442be6821a50f993659
|
|
4
|
+
data.tar.gz: 840863a3f380843728489b4b6e6518fa0d5bed4cb73a5dd3bddde05f8c525c56
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
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.
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
178
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
207
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
14
|
-
order.update!(status: :splitting)
|
|
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
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
|
-
|
|
120
|
-
Dscf::Marketplace::AggregatorListing.create!(aggregator: biz_agg, supplier_product:
|
|
121
|
-
Dscf::Marketplace::AggregatorListing.create!(aggregator: biz_agg, supplier_product:
|
|
122
|
-
|
|
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
|