dscf-marketplace 0.9.0 → 0.9.1
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 +81 -29
- data/app/models/dscf/marketplace/order.rb +2 -2
- data/app/models/dscf/marketplace/order_item.rb +2 -2
- data/app/policies/dscf/marketplace/order_policy.rb +4 -0
- data/db/demo_seeds.rb +85 -0
- data/db/seeds.rb +12 -0
- data/lib/dscf/marketplace/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 06e78b71c3cb2e9a47b73bdc71eb3c221e6ef2c00fae135fbe6f4b9f6b76c0ad
|
|
4
|
+
data.tar.gz: f416eef38f577530f81e698f6c0fbccfbca0e18b342866e50736a3a328bd65e4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 58a193bbca932e849a71adfe6faad3bb2b9b79a8cb312fcb4973aeec8e650b740ae6d10977548b8c94633616fbdeb5089570e332d205ef83e77538e5af6953b6
|
|
7
|
+
data.tar.gz: 727fd25dd31d44c4f52544ecd161096a502ca4168ca934e1bcd4324ac4b9dcff57c37c268607e4f081ede2e88227441aeef35cd021dd8a7333313a451349de23
|
|
@@ -111,6 +111,8 @@ module Dscf
|
|
|
111
111
|
Dscf::Marketplace::OrderValidationService.validate(obj)
|
|
112
112
|
create_notification_for_order(obj, :validation_completed)
|
|
113
113
|
render_success(data: obj, serializer_options: { include: default_serializer_includes[:show] || [] })
|
|
114
|
+
rescue => e
|
|
115
|
+
render_error(errors: e.message, status: :unprocessable_entity)
|
|
114
116
|
end
|
|
115
117
|
|
|
116
118
|
def resolve_item
|
|
@@ -135,6 +137,8 @@ module Dscf
|
|
|
135
137
|
item.save!
|
|
136
138
|
create_notification_for_order(obj, :item_resolved)
|
|
137
139
|
render_success(data: item)
|
|
140
|
+
rescue => e
|
|
141
|
+
render_error(errors: e.message, status: :unprocessable_entity)
|
|
138
142
|
end
|
|
139
143
|
|
|
140
144
|
def assign_source
|
|
@@ -144,6 +148,8 @@ module Dscf
|
|
|
144
148
|
item = obj.order_items.find(params[:order_item_id])
|
|
145
149
|
Dscf::Marketplace::OrderSplittingService.assign_source(item, source_type: params[:source_type], source_id: params[:source_id])
|
|
146
150
|
render_success(data: item)
|
|
151
|
+
rescue => e
|
|
152
|
+
render_error(errors: e.message, status: :unprocessable_entity)
|
|
147
153
|
end
|
|
148
154
|
|
|
149
155
|
def split
|
|
@@ -155,6 +161,8 @@ module Dscf
|
|
|
155
161
|
Dscf::Marketplace::OrderSplittingService.perform_split(obj)
|
|
156
162
|
create_notification_for_order(obj, :split_ready)
|
|
157
163
|
render_success(data: obj, serializer_options: { include: default_serializer_includes[:show] || [] })
|
|
164
|
+
rescue => e
|
|
165
|
+
render_error(errors: e.message, status: :unprocessable_entity)
|
|
158
166
|
end
|
|
159
167
|
|
|
160
168
|
def supplier_confirm
|
|
@@ -167,6 +175,8 @@ module Dscf
|
|
|
167
175
|
|
|
168
176
|
create_notification_for_order(obj, confirmed ? :supplier_confirmed : :supplier_rejected, reason: reason)
|
|
169
177
|
render_success(data: obj, serializer_options: { include: default_serializer_includes[:show] || [] })
|
|
178
|
+
rescue => e
|
|
179
|
+
render_error(errors: e.message, status: :unprocessable_entity)
|
|
170
180
|
end
|
|
171
181
|
|
|
172
182
|
def retailer_confirm
|
|
@@ -187,6 +197,8 @@ module Dscf
|
|
|
187
197
|
end
|
|
188
198
|
|
|
189
199
|
render_success(data: obj, serializer_options: { include: default_serializer_includes[:show] || [] })
|
|
200
|
+
rescue => e
|
|
201
|
+
render_error(errors: e.message, status: :unprocessable_entity)
|
|
190
202
|
end
|
|
191
203
|
|
|
192
204
|
|
|
@@ -223,44 +235,84 @@ module Dscf
|
|
|
223
235
|
private
|
|
224
236
|
|
|
225
237
|
def create_direct_listing_order
|
|
226
|
-
listing =
|
|
227
|
-
|
|
238
|
+
listing = nil
|
|
239
|
+
aggregator_listing = nil
|
|
240
|
+
|
|
241
|
+
if model_params[:listing_type] == "AggregatorListing"
|
|
242
|
+
aggregator_listing = Dscf::Marketplace::AggregatorListing.active.find_by(id: model_params[:listing_id])
|
|
243
|
+
return render_error(errors: "Listing is not available", status: :unprocessable_entity) unless aggregator_listing
|
|
244
|
+
else
|
|
245
|
+
listing = Dscf::Marketplace::Listing.active.find_by(id: model_params[:listing_id])
|
|
246
|
+
if listing.blank? && model_params[:listing_type].blank?
|
|
247
|
+
aggregator_listing = Dscf::Marketplace::AggregatorListing.active.find_by(id: model_params[:listing_id])
|
|
248
|
+
end
|
|
249
|
+
return render_error(errors: "Listing is not available", status: :unprocessable_entity) if listing.blank? && aggregator_listing.blank?
|
|
250
|
+
end
|
|
228
251
|
|
|
229
252
|
quantity = direct_listing_quantity
|
|
230
253
|
return render_error(errors: "Quantity must be greater than 0", status: :unprocessable_entity) unless quantity.positive?
|
|
231
254
|
|
|
232
|
-
|
|
255
|
+
limit_quantity = listing ? listing.quantity : aggregator_listing.quantity
|
|
256
|
+
if quantity > limit_quantity
|
|
233
257
|
return render_error(errors: "Requested quantity exceeds available listing quantity", status: :unprocessable_entity)
|
|
234
258
|
end
|
|
235
259
|
|
|
236
260
|
order = nil
|
|
237
261
|
ActiveRecord::Base.transaction do
|
|
238
|
-
listing
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
262
|
+
if listing
|
|
263
|
+
listing.lock!
|
|
264
|
+
|
|
265
|
+
if quantity > listing.quantity
|
|
266
|
+
listing.errors.add(:base, "Requested quantity exceeds available listing quantity")
|
|
267
|
+
raise ActiveRecord::RecordInvalid.new(listing)
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
order = @clazz.new(model_params.except(:order_items_attributes))
|
|
271
|
+
order.order_type = :direct_listing
|
|
272
|
+
order.status = :pending
|
|
273
|
+
order.listing = listing
|
|
274
|
+
order.ordered_to = listing.business
|
|
275
|
+
|
|
276
|
+
product = listing.supplier_product.product
|
|
277
|
+
order.order_items.build(
|
|
278
|
+
listing: listing,
|
|
279
|
+
product: product,
|
|
280
|
+
unit: product.unit,
|
|
281
|
+
quantity: quantity,
|
|
282
|
+
unit_price: listing.price,
|
|
283
|
+
status: :pending
|
|
284
|
+
)
|
|
285
|
+
order.save!
|
|
286
|
+
|
|
287
|
+
new_quantity = listing.quantity - quantity
|
|
288
|
+
listing.update!(quantity: new_quantity, status: (new_quantity.zero? ? :sold_out : listing.status))
|
|
289
|
+
else
|
|
290
|
+
aggregator_listing.lock!
|
|
291
|
+
|
|
292
|
+
if quantity > aggregator_listing.quantity
|
|
293
|
+
aggregator_listing.errors.add(:base, "Requested quantity exceeds available listing quantity")
|
|
294
|
+
raise ActiveRecord::RecordInvalid.new(aggregator_listing)
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
order = @clazz.new(model_params.except(:order_items_attributes, :listing_id))
|
|
298
|
+
order.order_type = :direct_listing
|
|
299
|
+
order.status = :pending
|
|
300
|
+
order.ordered_to = aggregator_listing.aggregator
|
|
301
|
+
|
|
302
|
+
product = aggregator_listing.supplier_product.product
|
|
303
|
+
order.order_items.build(
|
|
304
|
+
source: aggregator_listing,
|
|
305
|
+
product: product,
|
|
306
|
+
unit: product.unit,
|
|
307
|
+
quantity: quantity,
|
|
308
|
+
unit_price: aggregator_listing.price,
|
|
309
|
+
status: :pending
|
|
310
|
+
)
|
|
311
|
+
order.save!
|
|
312
|
+
|
|
313
|
+
new_quantity = aggregator_listing.quantity - quantity
|
|
314
|
+
aggregator_listing.update!(quantity: new_quantity, status: (new_quantity.zero? ? :sold_out : aggregator_listing.status))
|
|
243
315
|
end
|
|
244
|
-
|
|
245
|
-
order = @clazz.new(model_params.except(:order_items_attributes))
|
|
246
|
-
order.order_type = :direct_listing
|
|
247
|
-
order.status = :pending
|
|
248
|
-
order.listing = listing
|
|
249
|
-
order.ordered_to = listing.business
|
|
250
|
-
|
|
251
|
-
product = listing.supplier_product.product
|
|
252
|
-
order.order_items.build(
|
|
253
|
-
listing: listing,
|
|
254
|
-
product: product,
|
|
255
|
-
unit: product.unit,
|
|
256
|
-
quantity: quantity,
|
|
257
|
-
unit_price: listing.price,
|
|
258
|
-
status: :pending
|
|
259
|
-
)
|
|
260
|
-
order.save!
|
|
261
|
-
|
|
262
|
-
new_quantity = listing.quantity - quantity
|
|
263
|
-
listing.update!(quantity: new_quantity, status: (new_quantity.zero? ? :sold_out : listing.status))
|
|
264
316
|
end
|
|
265
317
|
|
|
266
318
|
order = @clazz.includes(eager_loaded_associations).find(order.id) if eager_loaded_associations.present?
|
|
@@ -286,7 +338,7 @@ module Dscf
|
|
|
286
338
|
def model_params
|
|
287
339
|
@model_params ||= params.require(:order).permit(
|
|
288
340
|
:quotation_id, :listing_id, :user_id, :ordered_by_id, :ordered_to_id, :delivery_order_id, :dropoff_address_id,
|
|
289
|
-
:order_type, :status, :fulfillment_type, :payment_method, :received_bank_name, :transaction_reference,
|
|
341
|
+
:order_type, :status, :fulfillment_type, :payment_method, :received_bank_name, :transaction_reference, :listing_type,
|
|
290
342
|
order_items_attributes: [ :id, :quotation_item_id, :listing_id, :product_id, :unit_id, :quantity, :unit_price, :status, :_destroy ]
|
|
291
343
|
)
|
|
292
344
|
@model_params[:payment_method] = normalize_payment_method(@model_params[:payment_method]) if @model_params.key?(:payment_method)
|
|
@@ -132,7 +132,7 @@ module Dscf::Marketplace
|
|
|
132
132
|
if rfq_based?
|
|
133
133
|
quotation&.business
|
|
134
134
|
elsif direct_listing?
|
|
135
|
-
listing&.business
|
|
135
|
+
listing&.business || ordered_to
|
|
136
136
|
end
|
|
137
137
|
end
|
|
138
138
|
|
|
@@ -209,7 +209,7 @@ module Dscf::Marketplace
|
|
|
209
209
|
private
|
|
210
210
|
|
|
211
211
|
def quotation_or_listing_present
|
|
212
|
-
unless quotation.present? || listing.present?
|
|
212
|
+
unless quotation.present? || listing.present? || order_items.any? { |item| item.source_type == "Dscf::Marketplace::AggregatorListing" }
|
|
213
213
|
errors.add(:base, "Either quotation or listing must be present")
|
|
214
214
|
end
|
|
215
215
|
end
|
|
@@ -56,8 +56,8 @@ module Dscf::Marketplace
|
|
|
56
56
|
private
|
|
57
57
|
|
|
58
58
|
def quotation_item_or_listing_present
|
|
59
|
-
unless quotation_item.present? || listing.present?
|
|
60
|
-
errors.add(:base, "Either quotation_item or listing must be present")
|
|
59
|
+
unless quotation_item.present? || listing.present? || (source_type == "Dscf::Marketplace::AggregatorListing" && source_id.present?)
|
|
60
|
+
errors.add(:base, "Either quotation_item, listing, or aggregator listing source must be present")
|
|
61
61
|
end
|
|
62
62
|
end
|
|
63
63
|
end
|
|
@@ -40,6 +40,10 @@ module Dscf
|
|
|
40
40
|
def retailer_confirm?
|
|
41
41
|
user.has_permission?("orders.confirm") # or dedicated permission
|
|
42
42
|
end
|
|
43
|
+
|
|
44
|
+
def show?
|
|
45
|
+
user.has_permission?("orders.show") || user.has_permission?("orders.filter") || user.super_admin?
|
|
46
|
+
end
|
|
43
47
|
end
|
|
44
48
|
end
|
|
45
49
|
end
|
data/db/demo_seeds.rb
CHANGED
|
@@ -22,6 +22,7 @@ Dscf::Marketplace::RfqItem.delete_all
|
|
|
22
22
|
Dscf::Marketplace::RequestForQuotation.delete_all
|
|
23
23
|
Dscf::Marketplace::AggregatorListing.delete_all
|
|
24
24
|
Dscf::Marketplace::Listing.delete_all
|
|
25
|
+
Dscf::Marketplace::ProductInclusionRequest.delete_all
|
|
25
26
|
Dscf::Marketplace::Agent.delete_all
|
|
26
27
|
Dscf::Marketplace::SupplierProduct.delete_all
|
|
27
28
|
Dscf::Marketplace::Supplier.delete_all
|
|
@@ -41,6 +42,13 @@ end
|
|
|
41
42
|
sa_role = Dscf::Core::Role.find_or_create_by!(code: "SUPER_ADMIN") { |r| r.name = "Super Admin"; r.active = true }
|
|
42
43
|
Dscf::Core::UserRole.find_or_create_by!(user: admin, role: sa_role)
|
|
43
44
|
|
|
45
|
+
# Ensure SUPER_ADMIN has basic orders permissions (show/filter etc) for detail views etc.
|
|
46
|
+
orders_perms = Dscf::Core::Permission.where(resource: "orders")
|
|
47
|
+
orders_perms.each do |perm|
|
|
48
|
+
Dscf::Core::RolePermission.find_or_create_by!(role: sa_role, permission: perm)
|
|
49
|
+
end
|
|
50
|
+
puts " Granted orders permissions to SUPER_ADMIN"
|
|
51
|
+
|
|
44
52
|
puts "🌱 Seeding demo data..."
|
|
45
53
|
|
|
46
54
|
# ── Businesses ──
|
|
@@ -113,8 +121,85 @@ Dscf::Marketplace::AggregatorListing.create!(aggregator: biz_agg, supplier_produ
|
|
|
113
121
|
Dscf::Marketplace::AggregatorListing.create!(aggregator: biz_agg, supplier_product: sp6b, price: 420, quantity: 80, status: 0)
|
|
114
122
|
puts " Aggregator Listings: 3"
|
|
115
123
|
|
|
124
|
+
# ── Demo Address ──
|
|
125
|
+
address = Dscf::Core::Address.first || Dscf::Core::Address.create!(addressable: admin, address_line_1: "Bole Road, Building 12", city: "Addis Ababa", country: "Ethiopia")
|
|
126
|
+
|
|
127
|
+
# ── Orders (Direct Purchases) ──
|
|
128
|
+
l1 = Dscf::Marketplace::Listing.first
|
|
129
|
+
if l1
|
|
130
|
+
order1 = Dscf::Marketplace::Order.create!(
|
|
131
|
+
order_type: :direct_listing,
|
|
132
|
+
status: :pending,
|
|
133
|
+
fulfillment_type: :self_pickup,
|
|
134
|
+
payment_method: :cash,
|
|
135
|
+
user: admin,
|
|
136
|
+
ordered_by: admin,
|
|
137
|
+
ordered_to: l1.business,
|
|
138
|
+
listing: l1,
|
|
139
|
+
total_amount: l1.price * 5
|
|
140
|
+
)
|
|
141
|
+
order1.order_items.create!(
|
|
142
|
+
listing: l1,
|
|
143
|
+
product: l1.supplier_product.product,
|
|
144
|
+
unit: l1.supplier_product.product.unit,
|
|
145
|
+
quantity: 5,
|
|
146
|
+
unit_price: l1.price,
|
|
147
|
+
status: :pending
|
|
148
|
+
)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
l2 = Dscf::Marketplace::Listing.last
|
|
152
|
+
if l2 && l2 != l1
|
|
153
|
+
order2 = Dscf::Marketplace::Order.create!(
|
|
154
|
+
order_type: :direct_listing,
|
|
155
|
+
status: :processing,
|
|
156
|
+
fulfillment_type: :delivery,
|
|
157
|
+
payment_method: :bank_transfer,
|
|
158
|
+
user: admin,
|
|
159
|
+
ordered_by: admin,
|
|
160
|
+
ordered_to: l2.business,
|
|
161
|
+
listing: l2,
|
|
162
|
+
dropoff_address: address,
|
|
163
|
+
total_amount: l2.price * 10
|
|
164
|
+
)
|
|
165
|
+
order2.order_items.create!(
|
|
166
|
+
listing: l2,
|
|
167
|
+
product: l2.supplier_product.product,
|
|
168
|
+
unit: l2.supplier_product.product.unit,
|
|
169
|
+
quantity: 10,
|
|
170
|
+
unit_price: l2.price,
|
|
171
|
+
status: :pending
|
|
172
|
+
)
|
|
173
|
+
end
|
|
174
|
+
puts " Orders: 2"
|
|
175
|
+
|
|
176
|
+
# ── RFQs (Request For Quotations) ──
|
|
177
|
+
rfq1 = Dscf::Marketplace::RequestForQuotation.create!(
|
|
178
|
+
user: admin,
|
|
179
|
+
status: :sent,
|
|
180
|
+
notes: "Looking for 500kg of premium washed Yirgacheffe coffee in bulk for export."
|
|
181
|
+
)
|
|
182
|
+
rfq1.rfq_items.create!(
|
|
183
|
+
product: p1,
|
|
184
|
+
unit: p1.unit,
|
|
185
|
+
quantity: 500
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
rfq2 = Dscf::Marketplace::RequestForQuotation.create!(
|
|
189
|
+
user: admin,
|
|
190
|
+
status: :draft,
|
|
191
|
+
notes: "Checking prices for traditional Shema fabrics."
|
|
192
|
+
)
|
|
193
|
+
rfq2.rfq_items.create!(
|
|
194
|
+
product: p4,
|
|
195
|
+
unit: p4.unit,
|
|
196
|
+
quantity: 100
|
|
197
|
+
)
|
|
198
|
+
puts " RFQs: 2"
|
|
199
|
+
|
|
116
200
|
puts ""
|
|
117
201
|
puts "✅ DONE."
|
|
118
202
|
puts " 6 visible: Yirgacheffe, Sidamo(650), Shema(promoted), Habesha(580), Berbere, Harrar"
|
|
119
203
|
puts " 2 hidden: Sidamo(720), Habesha(650)"
|
|
120
204
|
puts " 1 pending approval (sp6), 2 pending KYC, 1 inactive agent"
|
|
205
|
+
puts " 2 test orders, 2 test RFQs added for UI validation."
|
data/db/seeds.rb
CHANGED
|
@@ -39,3 +39,15 @@ marketplace_permissions.find_each do |permission|
|
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
puts "✅ Seeded #{marketplace_permissions.count} marketplace permissions and assigned to USER role."
|
|
42
|
+
|
|
43
|
+
# Also grant key orders permissions (incl. show for detail views) to SUPER_ADMIN if exists
|
|
44
|
+
sa_role = Dscf::Core::Role.find_by(code: "SUPER_ADMIN")
|
|
45
|
+
if sa_role
|
|
46
|
+
orders_actions = %i[show filter validate resolve_item assign_source split supplier_confirm retailer_confirm]
|
|
47
|
+
orders_actions.each do |action|
|
|
48
|
+
if perm = Dscf::Core::Permission.find_by(resource: "orders", action: action)
|
|
49
|
+
Dscf::Core::RolePermission.find_or_create_by!(role: sa_role, permission: perm)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
puts " Also granted key orders.* to SUPER_ADMIN"
|
|
53
|
+
end
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dscf-marketplace
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.9.
|
|
4
|
+
version: 0.9.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Asrat
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-06-
|
|
10
|
+
date: 2026-06-25 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: rails
|