dscf-marketplace 0.13.7 → 0.13.9

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: 21099282f07c99ae15cd4c526736df7c8e1597ff82caf3e1b540380417f093d0
4
- data.tar.gz: e870bcffce615ba356fec8fa15055aa545a3c7efc17cffe95d6bb61f9420d73b
3
+ metadata.gz: 0a4e70c0a7ba7cd2e04c0789cf215631ee94ad124892ab5655bde66037b8a931
4
+ data.tar.gz: e02095b4c24fd10f30e46f8e2f9fff8df0a192d2dfbe32a8f9b81768833b4eb0
5
5
  SHA512:
6
- metadata.gz: e3284d4198ad24bbe298fbf6a6a9c4be20a33e72ed0391d865f524859fd0064c925f8d0693f25bc23cf47504e1f180a0a52dc3fa0256ab8cf8b2ac0f5bfb0334
7
- data.tar.gz: 3667ca220153c9a32cc0162ef8018c9204fdb39005f4a109ce88f8e77f31c08d110d1f76d2d25009e6bc891b0c611eb124dfc688044a81a53efd7adcd6695397
6
+ metadata.gz: 7abf3080c4208782460e6ed94a223c4a0730dd6cd2ff27419980a501e7f2729c21699b4c0237ecfe8c85f81962b698aec7b17e73922eac595160238445203458
7
+ data.tar.gz: 96841b42186010cf01261d58d9749f2f253eec051a4e2f1de3cc977bf51b1ae96d423963941f46d921a5446bdaae4c7e4cd52f3953b010faf0adf65418539117
@@ -137,6 +137,39 @@ module Dscf
137
137
  render_error(errors: e.message, status: :unprocessable_entity)
138
138
  end
139
139
 
140
+ # Supplier-owner resubmission after an admin modification request.
141
+ # Applies any corrected fields / re-attached documents, then records a
142
+ # fresh :pending review so the record re-enters the admin queue. Only
143
+ # valid from the :modify state (mirrors the reviewable transition
144
+ # "modify" => "pending").
145
+ def resubmit
146
+ set_object
147
+ authorize @obj, :resubmit?
148
+
149
+ unless @obj.modification_requested?(:default)
150
+ return render_error(errors: "Supplier is not awaiting modification", status: :unprocessable_entity)
151
+ end
152
+
153
+ ActiveRecord::Base.transaction do
154
+ @obj.update!(model_params) if params[:supplier].present?
155
+
156
+ @obj.documents.attach(params[:business_license]) if params[:business_license].present?
157
+
158
+ if params[:additional_documents].present?
159
+ params[:additional_documents].each { |doc| @obj.documents.attach(doc) }
160
+ end
161
+
162
+ @obj.resubmit!
163
+ end
164
+
165
+ render_success("suppliers.success.resubmit", data: @obj.reload)
166
+ rescue ActiveRecord::RecordInvalid => e
167
+ errors = e.respond_to?(:record) && e.record ? e.record.errors.full_messages : [e.message]
168
+ render_error(errors: errors, status: :unprocessable_entity)
169
+ rescue StandardError => e
170
+ render_error(errors: e.message, status: :unprocessable_entity)
171
+ end
172
+
140
173
  private
141
174
 
142
175
  def create_notification(action_type, reason = nil)
@@ -73,6 +73,18 @@ module Dscf::Marketplace
73
73
  )
74
74
  end
75
75
 
76
+ # Owner resubmission after a modification request: records a fresh pending
77
+ # review (no reviewer — this is a submission, not an admin review), which
78
+ # moves current_status_for back to "pending" for re-review.
79
+ def resubmit!
80
+ Dscf::Core::Review.create!(
81
+ reviewable: self,
82
+ context: :default,
83
+ status: "pending",
84
+ reviewed_at: Time.current
85
+ )
86
+ end
87
+
76
88
  def review_status(context = :default)
77
89
  current_status_for(context)
78
90
  end
@@ -17,6 +17,11 @@ module Dscf
17
17
  user.present? && record.business_id.in?(user.businesses.pluck(:id))
18
18
  end
19
19
 
20
+ # Resubmission is an owner action (same ownership rule as update).
21
+ def resubmit?
22
+ update?
23
+ end
24
+
20
25
  def my_suppliers?
21
26
  true
22
27
  end
@@ -2,7 +2,7 @@ module Dscf
2
2
  module Marketplace
3
3
  class SupplierSerializer < ActiveModel::Serializer
4
4
  attributes :id, :name, :code, :address, :contact_phone, :business_type,
5
- :business_id, :review_status, :document_urls, :created_at, :updated_at
5
+ :business_id, :review_status, :review_reason, :document_urls, :created_at, :updated_at
6
6
 
7
7
  belongs_to :business
8
8
  has_many :addresses
@@ -11,6 +11,15 @@ module Dscf
11
11
  object.review_status
12
12
  end
13
13
 
14
+ # Reason the admin gave on the latest review (modification request or
15
+ # rejection). Lives in the Review's jsonb `feedback` as {reason: "..."}.
16
+ # nil for approved/pending/draft. Lets the mobile "changes requested"
17
+ # screen show the admin's note without a push notification.
18
+ def review_reason
19
+ feedback = object.current_review_for(:default)&.feedback
20
+ feedback && (feedback["reason"] || feedback[:reason])
21
+ end
22
+
14
23
  def document_urls
15
24
  object.document_urls
16
25
  end
data/config/routes.rb CHANGED
@@ -29,6 +29,7 @@ Dscf::Marketplace::Engine.routes.draw do
29
29
  post "approve"
30
30
  post "reject"
31
31
  post "modify"
32
+ post "resubmit"
32
33
  end
33
34
  end
34
35
 
@@ -0,0 +1,274 @@
1
+ # ════════════════════════════════════════════════════════════════════════════
2
+ # ANTIGRAVITY SEEDS — Clean, collision-free, and fully idempotent demo data
3
+ # ════════════════════════════════════════════════════════════════════════════
4
+ # NOTE: This script does NOT delete any existing tables or database records.
5
+ # It safely finds-or-creates our custom test ecosystem.
6
+
7
+ puts "🌱 Initializing Antigravity test data safely..."
8
+
9
+ # ── 1. Find Existing Super Admin ──
10
+ admin = Dscf::Core::User.find_by(email: "admin@dscf.com")
11
+ unless admin
12
+ puts "❌ admin@dscf.com not found! Creating default admin as fallback..."
13
+ admin = Dscf::Core::User.create!(
14
+ email: "admin@dscf.com",
15
+ phone: "+251999000000",
16
+ password: "Admin@2024",
17
+ verified_at: Time.current,
18
+ temp_password: false
19
+ )
20
+ end
21
+
22
+ sa_role = Dscf::Core::Role.find_or_create_by!(code: "SUPER_ADMIN") { |r| r.name = "Super Admin"; r.active = true }
23
+ Dscf::Core::UserRole.find_or_create_by!(user: admin, role: sa_role)
24
+
25
+ # Ensure SUPER_ADMIN has basic orders permissions
26
+ orders_perms = Dscf::Core::Permission.where(resource: "orders")
27
+ orders_perms.each do |perm|
28
+ Dscf::Core::RolePermission.find_or_create_by!(role: sa_role, permission: perm)
29
+ end
30
+ puts "✅ Super Admin configured."
31
+
32
+ # ── 2. Find or Create Custom Antigravity Users safely ──
33
+ retailer_user = Dscf::Core::User.find_by(email: "antigravity.retailer@example.com")
34
+ unless retailer_user
35
+ phone = "+251977111111"
36
+ # Avoid phone collisions if already taken
37
+ while Dscf::Core::User.exists?(phone: phone)
38
+ phone = "+251977111#{rand(100..999)}"
39
+ end
40
+
41
+ retailer_user = Dscf::Core::User.create!(
42
+ email: "antigravity.retailer@example.com",
43
+ phone: phone,
44
+ password: "password",
45
+ verified_at: Time.current,
46
+ temp_password: false
47
+ )
48
+ end
49
+
50
+ supplier_user = Dscf::Core::User.find_by(email: "antigravity.supplier@example.com")
51
+ unless supplier_user
52
+ phone = "+251977222222"
53
+ # Avoid phone collisions if already taken
54
+ while Dscf::Core::User.exists?(phone: phone)
55
+ phone = "+251977222#{rand(100..999)}"
56
+ end
57
+
58
+ supplier_user = Dscf::Core::User.create!(
59
+ email: "antigravity.supplier@example.com",
60
+ phone: phone,
61
+ password: "password",
62
+ verified_at: Time.current,
63
+ temp_password: false
64
+ )
65
+ end
66
+
67
+ # Assign default USER role
68
+ user_role = Dscf::Core::Role.find_or_create_by!(code: "USER") { |r| r.name = "User"; r.active = true }
69
+ Dscf::Core::UserRole.find_or_create_by!(user: retailer_user, role: user_role)
70
+ Dscf::Core::UserRole.find_or_create_by!(user: supplier_user, role: user_role)
71
+
72
+ # Assign marketplace permissions to USER role
73
+ Dscf::Core::Permission.where(engine: "dscf-marketplace").find_each do |perm|
74
+ Dscf::Core::RolePermission.find_or_create_by!(role: user_role, permission: perm)
75
+ end
76
+
77
+ puts "✅ Custom users verified/created."
78
+
79
+ # ── 3. Find or Create Custom Businesses safely ──
80
+ bt_manufacturer = Dscf::Core::BusinessType.find_or_create_by!(name: "Manufacturer")
81
+ bt_retailer = Dscf::Core::BusinessType.find_or_create_by!(name: "Retailer")
82
+
83
+ retailer_biz = Dscf::Core::Business.find_by(tin_number: "TIN-AG-RET-001")
84
+ unless retailer_biz
85
+ phone = "+251977111112"
86
+ while Dscf::Core::Business.exists?(contact_phone: phone)
87
+ phone = "+251977111#{rand(100..999)}"
88
+ end
89
+
90
+ retailer_biz = Dscf::Core::Business.create!(
91
+ name: "Antigravity Supermarket",
92
+ user: retailer_user,
93
+ business_type: bt_retailer,
94
+ contact_phone: phone,
95
+ description: "Antigravity Premium Retailer Store",
96
+ tin_number: "TIN-AG-RET-001"
97
+ )
98
+ end
99
+
100
+ supplier_biz = Dscf::Core::Business.find_by(tin_number: "TIN-AG-SUP-001")
101
+ unless supplier_biz
102
+ phone = "+251977222223"
103
+ while Dscf::Core::Business.exists?(contact_phone: phone)
104
+ phone = "+251977222#{rand(100..999)}"
105
+ end
106
+
107
+ supplier_biz = Dscf::Core::Business.create!(
108
+ name: "Antigravity Coffee & Textile Wholesaler",
109
+ user: supplier_user,
110
+ business_type: bt_manufacturer,
111
+ contact_phone: phone,
112
+ description: "Premium Antigravity Wholesaler Export",
113
+ tin_number: "TIN-AG-SUP-001"
114
+ )
115
+ end
116
+
117
+ aggregator_biz = Dscf::Core::Business.find_by(tin_number: "TIN-AG-AGG-001")
118
+ unless aggregator_biz
119
+ phone = "+251977333333"
120
+ while Dscf::Core::Business.exists?(contact_phone: phone)
121
+ phone = "+251977333#{rand(100..999)}"
122
+ end
123
+
124
+ aggregator_biz = Dscf::Core::Business.create!(
125
+ name: "Antigravity Aggregators",
126
+ user: admin,
127
+ business_type: bt_manufacturer,
128
+ contact_phone: phone,
129
+ description: "Official Antigravity Marketplace Aggregator Hub",
130
+ tin_number: "TIN-AG-AGG-001"
131
+ )
132
+ end
133
+
134
+ puts "✅ Custom businesses verified/created."
135
+
136
+ # ── 4. Categories & Units ──
137
+ cat_coffee = Dscf::Marketplace::Category.find_or_create_by!(name: "Antigravity Coffee") do |c|
138
+ c.description = "Specialty roasted Ethiopian coffee"
139
+ end
140
+
141
+ cat_textile = Dscf::Marketplace::Category.find_or_create_by!(name: "Antigravity Textiles") do |c|
142
+ c.description = "Premium handwoven fabrics"
143
+ end
144
+
145
+ u_kg = Dscf::Marketplace::Unit.find_or_create_by!(code: "KG") do |u|
146
+ u.name = "Kilogram"
147
+ u.unit_type = :weight
148
+ end
149
+
150
+ u_meter = Dscf::Marketplace::Unit.find_or_create_by!(code: "M") do |u|
151
+ u.name = "Meter"
152
+ u.unit_type = :dimension
153
+ end
154
+
155
+ puts "✅ Categories & Units verified/created."
156
+
157
+ # ── 5. Products ──
158
+ p1 = Dscf::Marketplace::Product.find_or_create_by!(sku: "AG-COF-SID") do |p|
159
+ p.name = "Antigravity Sidamo Roasted Coffee"
160
+ p.description = "Premium washed Sidamo — Medium Roast"
161
+ p.category = cat_coffee
162
+ p.unit = u_kg
163
+ p.base_quantity = 1.0
164
+ p.gross_weight = 1.2
165
+ end
166
+
167
+ p2 = Dscf::Marketplace::Product.find_or_create_by!(sku: "AG-TEX-GAB") do |p|
168
+ p.name = "Antigravity Traditional Gabi Fabric"
169
+ p.description = "Handwoven thick cotton Gabi"
170
+ p.category = cat_textile
171
+ p.unit = u_meter
172
+ p.base_quantity = 1.0
173
+ p.gross_weight = 0.8
174
+ end
175
+
176
+ puts "✅ Products verified/created."
177
+
178
+ # ── 6. Supplier Record ──
179
+ supplier_record = Dscf::Marketplace::Supplier.find_or_create_by!(business_id: supplier_biz.id) do |s|
180
+ s.name = "Antigravity Wholesaler Outlet"
181
+ s.business_type = :manufacturer
182
+ s.contact_phone = supplier_biz.contact_phone
183
+ s.address = "Bole Sub-city, Addis Ababa"
184
+ end
185
+
186
+ # ── 7. Supplier Products ──
187
+ sp1 = Dscf::Marketplace::SupplierProduct.find_or_create_by!(business_id: supplier_biz.id, product_id: p1.id) do |sp|
188
+ sp.supplier = supplier_record
189
+ sp.supplier_price = 400
190
+ sp.available_quantity = 1000
191
+ sp.minimum_order_quantity = 5
192
+ sp.status = :active
193
+ end
194
+
195
+ sp2 = Dscf::Marketplace::SupplierProduct.find_or_create_by!(business_id: supplier_biz.id, product_id: p2.id) do |sp|
196
+ sp.supplier = supplier_record
197
+ sp.supplier_price = 300
198
+ sp.available_quantity = 500
199
+ sp.minimum_order_quantity = 2
200
+ sp.status = :active
201
+ end
202
+
203
+ puts "✅ Supplier Products verified/created."
204
+
205
+ # ── 8. Aggregator Listings (Sprint 2 Core Target) ──
206
+ al1 = Dscf::Marketplace::AggregatorListing.find_or_create_by!(aggregator_id: aggregator_biz.id, supplier_product_id: sp1.id) do |al|
207
+ al.price = 550
208
+ al.quantity = 200
209
+ al.status = 0 # active/published
210
+ end
211
+
212
+ al2 = Dscf::Marketplace::AggregatorListing.find_or_create_by!(aggregator_id: aggregator_biz.id, supplier_product_id: sp2.id) do |al|
213
+ al.price = 420
214
+ al.quantity = 150
215
+ al.status = 0 # active/published
216
+ end
217
+
218
+ puts "✅ Aggregator Listings verified/created."
219
+
220
+ # ── 9. Direct Listings ──
221
+ Dscf::Marketplace::Listing.find_or_create_by!(business_id: supplier_biz.id, supplier_product_id: sp1.id) do |l|
222
+ l.price = 500
223
+ l.quantity = 300
224
+ l.status = :active
225
+ l.description = "Direct Sidamo Roasted Coffee from Antigravity Outlet"
226
+ end
227
+
228
+ Dscf::Marketplace::Listing.find_or_create_by!(business_id: supplier_biz.id, supplier_product_id: sp2.id) do |l|
229
+ l.price = 380
230
+ l.quantity = 200
231
+ l.status = :active
232
+ l.description = "Direct Traditional Handwoven Gabi from Antigravity Outlet"
233
+ end
234
+
235
+ puts "✅ Direct Listings verified/created."
236
+
237
+ # ── 10. Demo Addresses & Agents ──
238
+ address = Dscf::Core::Address.find_by(user_id: retailer_user.id)
239
+ unless address
240
+ address = Dscf::Core::Address.create!(
241
+ user: retailer_user,
242
+ address_type: :shipping,
243
+ country: "Ethiopia",
244
+ city: "Addis Ababa",
245
+ sub_city: "Bole Sub-city",
246
+ woreda: "Woreda 03",
247
+ house_numbers: "Suite 4B"
248
+ )
249
+ end
250
+
251
+ agent = Dscf::Marketplace::Agent.find_by(onboarded_by_id: aggregator_biz.id)
252
+ unless agent
253
+ agent_phone = "+251977444444"
254
+ while Dscf::Marketplace::Agent.exists?(phone: agent_phone)
255
+ agent_phone = "+251977444#{rand(100..999)}"
256
+ end
257
+
258
+ Dscf::Marketplace::Agent.create!(
259
+ name: "Antigravity Logistics Agent",
260
+ phone: agent_phone,
261
+ service_area: "Bole Sub-city",
262
+ status: :active,
263
+ verification_status: :verified,
264
+ onboarded_by: aggregator_biz
265
+ )
266
+ end
267
+
268
+ puts "🎉 ALL ANTIGRAVITY SEEDS COMPLETED SUCCESSFULLY WITHOUT DELETING ANY DATA."
269
+ puts "--------------------------------------------------------"
270
+ puts "🔑 CREDENTIALS TO USE:"
271
+ puts "Buyer (Retailer): antigravity.retailer@example.com / password"
272
+ puts "Supplier (Wholesaler): antigravity.supplier@example.com / password"
273
+ puts "Super Admin: admin@dscf.com / Admin@2024"
274
+ puts "--------------------------------------------------------"
@@ -1,5 +1,5 @@
1
1
  module Dscf
2
2
  module Marketplace
3
- VERSION = "0.13.7".freeze
3
+ VERSION = "0.13.9".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.13.7
4
+ version: 0.13.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Asrat
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-07-04 00:00:00.000000000 Z
10
+ date: 2026-07-08 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rails
@@ -536,6 +536,7 @@ files:
536
536
  - config/environments/production.rb
537
537
  - config/locales/en.yml
538
538
  - config/routes.rb
539
+ - db/antigravity_seeds.rb
539
540
  - db/demo_seeds.rb
540
541
  - db/migrate/20250827172043_create_dscf_marketplace_categories.rb
541
542
  - db/migrate/20250827173526_update_dscf_marketplace_categories_indexes.rb