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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 28fd6ca06416aafd1e185ec3c143cbca9ad794a793e93b809be54bbee9779cae
4
- data.tar.gz: 01fdbf80d424f8d053be1b3bba44780758d52efb21e8ad7a6bf0a4bd761f2e68
3
+ metadata.gz: 06e78b71c3cb2e9a47b73bdc71eb3c221e6ef2c00fae135fbe6f4b9f6b76c0ad
4
+ data.tar.gz: f416eef38f577530f81e698f6c0fbccfbca0e18b342866e50736a3a328bd65e4
5
5
  SHA512:
6
- metadata.gz: 2935f0370099d113ee36f2f2722357ad66115557043957a9a6152e94a20080d0c16044e57f3d101819d3ab231d7911317589598db6a947ba90e19ef40e5b0aa1
7
- data.tar.gz: 3e7d3ce883999d57799693ecfc4ec9a52559f198de99ed512b9b80ded38d4a001afdd3e90f986d582ca34ebc9df34ff7580e4968ab04e6da3ba4943fac5ac184
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 = Dscf::Marketplace::Listing.active.find_by(id: model_params[:listing_id])
227
- return render_error(errors: "Listing is not available", status: :unprocessable_entity) unless listing
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
- if quantity > listing.quantity
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.lock!
239
-
240
- if quantity > listing.quantity
241
- listing.errors.add(:base, "Requested quantity exceeds available listing quantity")
242
- raise ActiveRecord::RecordInvalid.new(listing)
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
@@ -1,5 +1,5 @@
1
1
  module Dscf
2
2
  module Marketplace
3
- VERSION = "0.9.0".freeze
3
+ VERSION = "0.9.1".freeze
4
4
  end
5
5
  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.0
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-22 00:00:00.000000000 Z
10
+ date: 2026-06-25 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rails