dscf-marketplace 0.13.1 → 0.13.3
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 +100 -3
- data/app/models/dscf/marketplace/order.rb +16 -1
- data/app/models/dscf/marketplace/otp_verification.rb +44 -0
- data/app/policies/dscf/marketplace/order_policy.rb +4 -0
- data/app/serializers/dscf/marketplace/order_item_serializer.rb +4 -0
- data/app/serializers/dscf/marketplace/order_serializer.rb +2 -1
- data/app/services/dscf/marketplace/order_splitting_service.rb +13 -2
- data/config/routes.rb +2 -0
- data/db/migrate/20260704000001_add_agent_id_to_dscf_marketplace_orders.rb +8 -0
- data/db/migrate/20260704000002_create_dscf_marketplace_otp_verifications.rb +19 -0
- data/lib/dscf/marketplace/version.rb +1 -1
- data/spec/factories/dscf/marketplace/otp_verifications.rb +8 -0
- metadata +6 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 475043853266506246b2775c97fc0fb8a4c361fb7e415ae36b0c30c0a36f6a34
|
|
4
|
+
data.tar.gz: 5e5045ee415293974c10856a416907a1aeb9434a9e51fed26aecf72ed4ef1339
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 19eb9d280c9b18f8cf07c1532acff967a2bee651bc930f2c24a5142686999308b176d1622ebdbe9be233662fac3ed99bb4f55f2e50b4e3f4ac3bd29bc2e516ff
|
|
7
|
+
data.tar.gz: a907e0a7fa9aba5f8c14b3eaabc4b47224278bfdc32d758300c20744d12747a546aa846a219386f49e1cc6855fd96adebab43274f6835181fb016bcfbb5e0841
|
|
@@ -9,7 +9,7 @@ module Dscf
|
|
|
9
9
|
|
|
10
10
|
return create_direct_listing_order if direct_listing_request?
|
|
11
11
|
|
|
12
|
-
obj = @clazz.new(model_params)
|
|
12
|
+
obj = @clazz.new(model_params.except(:retailer_id))
|
|
13
13
|
if obj.save
|
|
14
14
|
obj = @clazz.includes(eager_loaded_associations).find(obj.id) if eager_loaded_associations.present?
|
|
15
15
|
includes = default_serializer_includes[:create] || []
|
|
@@ -45,6 +45,8 @@ module Dscf
|
|
|
45
45
|
def confirm
|
|
46
46
|
@obj = find_record
|
|
47
47
|
authorize @obj, :confirm?
|
|
48
|
+
return render_error("orders.errors.otp_confirmation_required", status: :unprocessable_entity) if @obj.agent_assisted? && !@obj.otp_verified?
|
|
49
|
+
|
|
48
50
|
if @obj.confirm!
|
|
49
51
|
render_success("orders.success.confirmed", data: @obj)
|
|
50
52
|
else
|
|
@@ -52,6 +54,46 @@ module Dscf
|
|
|
52
54
|
end
|
|
53
55
|
end
|
|
54
56
|
|
|
57
|
+
# The agent enters the OTP the retailer received to confirm an
|
|
58
|
+
# agent-assisted order. This does not touch `status` — it only
|
|
59
|
+
# unblocks `validate`/`confirm` (see the guards there), keeping the
|
|
60
|
+
# OTP gate independent of the existing fulfillment state machine.
|
|
61
|
+
def confirm_agent_order
|
|
62
|
+
@obj = find_record
|
|
63
|
+
authorize @obj, :confirm_agent_order?
|
|
64
|
+
return render_error(errors: "Order was not placed by an agent", status: :unprocessable_entity) unless @obj.agent_assisted?
|
|
65
|
+
|
|
66
|
+
otp = @obj.otp_verifications.order(created_at: :desc).first
|
|
67
|
+
return render_error(errors: "No OTP has been sent for this order", status: :unprocessable_entity) unless otp
|
|
68
|
+
|
|
69
|
+
case otp.verify(params[:otp_code])
|
|
70
|
+
when :verified
|
|
71
|
+
create_notification_for_order(@obj, :agent_order_confirmed)
|
|
72
|
+
notify_agent_order_confirmed(@obj)
|
|
73
|
+
render_success(data: @obj, serializer_options: { include: default_serializer_includes[:show] || [] })
|
|
74
|
+
when :expired
|
|
75
|
+
render_error(errors: "OTP has expired, request a new one", status: :unprocessable_entity)
|
|
76
|
+
when :max_attempts_exceeded
|
|
77
|
+
render_error(errors: "Too many incorrect attempts, request a new OTP", status: :unprocessable_entity)
|
|
78
|
+
else
|
|
79
|
+
render_error(errors: "Incorrect OTP", status: :unprocessable_entity)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def resend_agent_order_otp
|
|
84
|
+
@obj = find_record
|
|
85
|
+
authorize @obj, :confirm_agent_order?
|
|
86
|
+
return render_error(errors: "Order was not placed by an agent", status: :unprocessable_entity) unless @obj.agent_assisted?
|
|
87
|
+
|
|
88
|
+
phone = @obj.otp_verifications.order(created_at: :desc).first&.phone ||
|
|
89
|
+
Dscf::Marketplace::Retailer.find_by(user_id: @obj.ordered_by_id)&.phone
|
|
90
|
+
return render_error(errors: "No retailer phone number on file for this order", status: :unprocessable_entity) if phone.blank?
|
|
91
|
+
|
|
92
|
+
otp = Dscf::Marketplace::OtpVerification.generate_for(@obj, phone: phone)
|
|
93
|
+
create_notification_for_order(@obj, :agent_order_placed, otp_code: otp.code)
|
|
94
|
+
render_success(data: @obj, serializer_options: { include: default_serializer_includes[:show] || [] })
|
|
95
|
+
end
|
|
96
|
+
|
|
55
97
|
def cancel
|
|
56
98
|
@obj = find_record
|
|
57
99
|
authorize @obj, :cancel?
|
|
@@ -107,6 +149,7 @@ module Dscf
|
|
|
107
149
|
def validate
|
|
108
150
|
obj = find_record
|
|
109
151
|
authorize obj, :validate?
|
|
152
|
+
return render_error(errors: "Order awaits retailer OTP confirmation", status: :unprocessable_entity) if obj.agent_assisted? && !obj.otp_verified?
|
|
110
153
|
|
|
111
154
|
Dscf::Marketplace::OrderValidationService.validate(obj)
|
|
112
155
|
create_notification_for_order(obj, :validation_completed)
|
|
@@ -283,7 +326,7 @@ module Dscf
|
|
|
283
326
|
end
|
|
284
327
|
|
|
285
328
|
|
|
286
|
-
def create_notification_for_order(order, action, reason: nil)
|
|
329
|
+
def create_notification_for_order(order, action, reason: nil, otp_code: nil)
|
|
287
330
|
recipient = order.ordered_by || order.user
|
|
288
331
|
return unless recipient
|
|
289
332
|
|
|
@@ -298,12 +341,16 @@ module Dscf
|
|
|
298
341
|
when :adjustment_rejected then "Adjustment rejected on order ##{order.id}"
|
|
299
342
|
when :retailer_confirmed then "Retailer confirmed order ##{order.id}"
|
|
300
343
|
when :retailer_rejected then "Retailer cancelled order ##{order.id}"
|
|
344
|
+
when :agent_order_placed then "Your agent placed order ##{order.id}"
|
|
345
|
+
when :agent_order_confirmed then "Order ##{order.id} confirmed"
|
|
301
346
|
else "Order ##{order.id} update"
|
|
302
347
|
end
|
|
303
348
|
|
|
304
349
|
body = case action
|
|
305
350
|
when :supplier_rejected, :retailer_rejected, :supplier_adjusted, :adjustment_rejected
|
|
306
351
|
"Reason: #{reason}" if reason
|
|
352
|
+
when :agent_order_placed
|
|
353
|
+
"Your agent placed an order on your behalf. Your confirmation code is #{otp_code}. Share it only with your agent to confirm the order."
|
|
307
354
|
else "Status: #{order.status}"
|
|
308
355
|
end
|
|
309
356
|
|
|
@@ -317,6 +364,22 @@ module Dscf
|
|
|
317
364
|
Dscf::Core::NotificationService.deliver(notification)
|
|
318
365
|
end
|
|
319
366
|
|
|
367
|
+
# After the retailer's OTP confirms an agent-assisted order, both the
|
|
368
|
+
# agent and the aggregator (the business the order was placed with)
|
|
369
|
+
# get notified, in addition to the retailer's own notification above.
|
|
370
|
+
def notify_agent_order_confirmed(order)
|
|
371
|
+
[ order.agent&.user, order.ordered_to&.user ].compact.each do |recipient|
|
|
372
|
+
notification = Dscf::Core::Notification.create!(
|
|
373
|
+
notifiable: order,
|
|
374
|
+
recipient: recipient,
|
|
375
|
+
notification_type: :general,
|
|
376
|
+
title: "Order ##{order.id} confirmed",
|
|
377
|
+
body: "The retailer confirmed order ##{order.id} via OTP."
|
|
378
|
+
)
|
|
379
|
+
Dscf::Core::NotificationService.deliver(notification)
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
|
|
320
383
|
private
|
|
321
384
|
|
|
322
385
|
# Builds one or more direct-listing orders from a cart of one or more
|
|
@@ -327,6 +390,9 @@ module Dscf
|
|
|
327
390
|
# transaction (a multi-vendor cart either checks out completely or not
|
|
328
391
|
# at all — mirrors how the single-item path already behaved).
|
|
329
392
|
def create_direct_listing_order
|
|
393
|
+
agent_order_retailer = resolve_agent_assisted_retailer!
|
|
394
|
+
return if performed? # resolve_agent_assisted_retailer! already rendered an error
|
|
395
|
+
|
|
330
396
|
resolved_items = resolve_direct_listing_items
|
|
331
397
|
return if performed? # resolve_direct_listing_items already rendered an error
|
|
332
398
|
|
|
@@ -337,6 +403,8 @@ module Dscf
|
|
|
337
403
|
end
|
|
338
404
|
end
|
|
339
405
|
|
|
406
|
+
orders.each { |order| send_agent_order_otp(order, agent_order_retailer) } if agent_order_retailer
|
|
407
|
+
|
|
340
408
|
orders = orders.map { |o| @clazz.includes(eager_loaded_associations).find(o.id) } if eager_loaded_associations.present?
|
|
341
409
|
includes = default_serializer_includes[:create] || []
|
|
342
410
|
options = {include: includes} if includes.present?
|
|
@@ -354,6 +422,34 @@ module Dscf
|
|
|
354
422
|
render_error(error: e.message)
|
|
355
423
|
end
|
|
356
424
|
|
|
425
|
+
# When an agent places this order, ordered_by/user must be the
|
|
426
|
+
# retailer being ordered for — not whatever the client happens to send
|
|
427
|
+
# as ordered_by_id/user_id — so we resolve it server-side from
|
|
428
|
+
# retailer_id and overwrite those params. Returns the Retailer (used
|
|
429
|
+
# afterward for the OTP phone number), or nil for non-agent orders.
|
|
430
|
+
def resolve_agent_assisted_retailer!
|
|
431
|
+
return nil if model_params[:agent_id].blank?
|
|
432
|
+
|
|
433
|
+
retailer = Dscf::Marketplace::Retailer.find_by(id: model_params[:retailer_id])
|
|
434
|
+
if retailer.blank?
|
|
435
|
+
render_error(errors: "Retailer not found", status: :unprocessable_entity)
|
|
436
|
+
return nil
|
|
437
|
+
end
|
|
438
|
+
if retailer.user_id.blank?
|
|
439
|
+
render_error(errors: "Retailer has no linked user account to order on behalf of", status: :unprocessable_entity)
|
|
440
|
+
return nil
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
@model_params[:ordered_by_id] = retailer.user_id
|
|
444
|
+
@model_params[:user_id] = retailer.user_id
|
|
445
|
+
retailer
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
def send_agent_order_otp(order, retailer)
|
|
449
|
+
otp = Dscf::Marketplace::OtpVerification.generate_for(order, phone: retailer.phone)
|
|
450
|
+
create_notification_for_order(order, :agent_order_placed, otp_code: otp.code)
|
|
451
|
+
end
|
|
452
|
+
|
|
357
453
|
# Resolves each cart line to its Listing or AggregatorListing + quantity +
|
|
358
454
|
# the business it's ordered to. Falls back to the legacy top-level
|
|
359
455
|
# listing_id/listing_type params when a line doesn't carry its own
|
|
@@ -413,7 +509,7 @@ module Dscf
|
|
|
413
509
|
raise ActiveRecord::RecordInvalid.new(source)
|
|
414
510
|
end
|
|
415
511
|
|
|
416
|
-
order = @clazz.new(model_params.except(:order_items_attributes, :listing_id, :listing_type))
|
|
512
|
+
order = @clazz.new(model_params.except(:order_items_attributes, :listing_id, :listing_type, :retailer_id))
|
|
417
513
|
order.order_type = :direct_listing
|
|
418
514
|
order.status = :pending
|
|
419
515
|
order.ordered_to = items.first[:ordered_to]
|
|
@@ -473,6 +569,7 @@ module Dscf
|
|
|
473
569
|
@model_params ||= params.require(:order).permit(
|
|
474
570
|
:quotation_id, :listing_id, :user_id, :ordered_by_id, :ordered_to_id, :delivery_order_id, :dropoff_address_id,
|
|
475
571
|
:order_type, :status, :fulfillment_type, :payment_method, :received_bank_name, :transaction_reference, :listing_type,
|
|
572
|
+
:agent_id, :retailer_id,
|
|
476
573
|
order_items_attributes: [ :id, :quotation_item_id, :listing_id, :listing_type, :product_id, :unit_id, :quantity, :unit_price, :status, :_destroy ]
|
|
477
574
|
)
|
|
478
575
|
@model_params[:payment_method] = normalize_payment_method(@model_params[:payment_method]) if @model_params.key?(:payment_method)
|
|
@@ -23,8 +23,10 @@ module Dscf::Marketplace
|
|
|
23
23
|
belongs_to :ordered_to, class_name: "Dscf::Core::Business"
|
|
24
24
|
belongs_to :delivery_order, optional: true
|
|
25
25
|
belongs_to :dropoff_address, class_name: "Dscf::Core::Address", optional: true
|
|
26
|
+
belongs_to :agent, class_name: "Dscf::Marketplace::Agent", optional: true
|
|
26
27
|
has_many :order_items, dependent: :destroy, autosave: true
|
|
27
28
|
has_one :order_invoice, class_name: "Dscf::Marketplace::OrderInvoice", dependent: :destroy
|
|
29
|
+
has_many :otp_verifications, as: :verifiable, class_name: "Dscf::Marketplace::OtpVerification", dependent: :destroy
|
|
28
30
|
accepts_nested_attributes_for :order_items, allow_destroy: true
|
|
29
31
|
|
|
30
32
|
validates :order_type, presence: true
|
|
@@ -46,7 +48,7 @@ module Dscf::Marketplace
|
|
|
46
48
|
|
|
47
49
|
# Ransack configuration for secure filtering
|
|
48
50
|
def self.ransackable_attributes(_auth_object = nil)
|
|
49
|
-
%w[id quotation_id listing_id user_id ordered_by_id ordered_to_id delivery_order_id dropoff_address_id order_type status fulfillment_type payment_method received_bank_name transaction_reference total_amount created_at updated_at]
|
|
51
|
+
%w[id quotation_id listing_id user_id ordered_by_id ordered_to_id delivery_order_id dropoff_address_id agent_id order_type status fulfillment_type payment_method received_bank_name transaction_reference total_amount created_at updated_at]
|
|
50
52
|
end
|
|
51
53
|
|
|
52
54
|
def self.ransackable_associations(_auth_object = nil)
|
|
@@ -225,6 +227,19 @@ module Dscf::Marketplace
|
|
|
225
227
|
end
|
|
226
228
|
end
|
|
227
229
|
|
|
230
|
+
# An agent-assisted order (agent_id present) must have its OTP verified
|
|
231
|
+
# by the retailer before it can enter the normal validate/confirm
|
|
232
|
+
# pipeline — this gate is intentionally orthogonal to `status`, which
|
|
233
|
+
# already has an unrelated `waiting_retailer_confirmation` meaning (the
|
|
234
|
+
# retailer signing off on supplier-proposed adjustments mid-fulfillment).
|
|
235
|
+
def agent_assisted?
|
|
236
|
+
agent_id.present?
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def otp_verified?
|
|
240
|
+
otp_verifications.verified.exists?
|
|
241
|
+
end
|
|
242
|
+
|
|
228
243
|
private
|
|
229
244
|
|
|
230
245
|
def auto_assign_dropoff_address
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module Dscf::Marketplace
|
|
2
|
+
class OtpVerification < ApplicationRecord
|
|
3
|
+
self.table_name = "dscf_marketplace_otp_verifications"
|
|
4
|
+
|
|
5
|
+
# Placeholder until a real SMS/OTP provider is wired up (see
|
|
6
|
+
# Dscf::Core::NotificationService::Adapters::SmsStub) — every OTP is the
|
|
7
|
+
# same known code so the flow can be exercised end-to-end without a real
|
|
8
|
+
# phone. Swapping in random generation later only touches generate_for.
|
|
9
|
+
DEFAULT_CODE = "123456"
|
|
10
|
+
TTL = 10.minutes
|
|
11
|
+
MAX_ATTEMPTS = 5
|
|
12
|
+
|
|
13
|
+
belongs_to :verifiable, polymorphic: true
|
|
14
|
+
|
|
15
|
+
enum :status, {pending: 0, verified: 1, expired: 2, failed: 3}, default: :pending
|
|
16
|
+
|
|
17
|
+
validates :phone, :code, presence: true
|
|
18
|
+
|
|
19
|
+
def self.generate_for(verifiable, phone:)
|
|
20
|
+
verifiable.otp_verifications.pending.update_all(status: statuses[:expired])
|
|
21
|
+
create!(verifiable: verifiable, phone: phone, code: DEFAULT_CODE, expires_at: TTL.from_now)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def verify(code)
|
|
25
|
+
return :verified if verified?
|
|
26
|
+
return :expired if past_expiry?
|
|
27
|
+
return :max_attempts_exceeded if failed? || attempts >= MAX_ATTEMPTS
|
|
28
|
+
|
|
29
|
+
increment!(:attempts)
|
|
30
|
+
|
|
31
|
+
if code.to_s == self.code
|
|
32
|
+
update!(status: :verified, verified_at: Time.current)
|
|
33
|
+
:verified
|
|
34
|
+
else
|
|
35
|
+
update!(status: :failed) if attempts >= MAX_ATTEMPTS
|
|
36
|
+
:invalid
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def past_expiry?
|
|
41
|
+
pending? && expires_at < Time.current
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -19,6 +19,10 @@ module Dscf
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def source_name
|
|
22
|
+
# object.source (a polymorphic belongs_to) constantizes source_type
|
|
23
|
+
# internally and raises on anything that isn't a real class name —
|
|
24
|
+
# guard against legacy/bad data instead of 500ing the whole response.
|
|
25
|
+
return nil if object.source_type.present? && object.source_type.safe_constantize.nil?
|
|
22
26
|
return nil unless object.source
|
|
23
27
|
case object.source_type
|
|
24
28
|
when 'Dscf::Marketplace::AggregatorListing'
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module Dscf
|
|
2
2
|
module Marketplace
|
|
3
3
|
class OrderSerializer < ActiveModel::Serializer
|
|
4
|
-
attributes :id, :quotation_id, :listing_id, :user_id, :ordered_by_id, :ordered_to_id, :delivery_order_id, :dropoff_address_id,
|
|
4
|
+
attributes :id, :quotation_id, :listing_id, :user_id, :ordered_by_id, :ordered_to_id, :delivery_order_id, :dropoff_address_id, :agent_id,
|
|
5
5
|
:order_type, :status, :workflow_status, :has_validation_issues, :fulfillment_type, :payment_method, :received_bank_name, :transaction_reference, :total_amount,
|
|
6
6
|
:buyer_name, :buyer_phone, :buyer_email, :seller_name, :seller_phone, :seller_email,
|
|
7
7
|
:created_at, :updated_at
|
|
@@ -13,6 +13,7 @@ module Dscf
|
|
|
13
13
|
belongs_to :ordered_to
|
|
14
14
|
belongs_to :delivery_order
|
|
15
15
|
belongs_to :dropoff_address
|
|
16
|
+
belongs_to :agent
|
|
16
17
|
has_many :order_items
|
|
17
18
|
|
|
18
19
|
# Retailer's registered display name (users have no name field of their
|
|
@@ -8,8 +8,19 @@ module Dscf
|
|
|
8
8
|
# - Other supplier
|
|
9
9
|
|
|
10
10
|
def self.assign_source(order_item, source_type:, source_id:)
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
# "self" is the frontend SourcePicker's sentinel for "the aggregator
|
|
12
|
+
# fulfils it directly" — it is not a real polymorphic class name, so
|
|
13
|
+
# storing it literally corrupts the source association (any later
|
|
14
|
+
# read of order_item.source tries "self".constantize and crashes).
|
|
15
|
+
# A self-fulfilled line has no external source at all: clear it,
|
|
16
|
+
# matching the frontend's own isAggregatorManaged check (!source_type).
|
|
17
|
+
if source_type.to_s == "self"
|
|
18
|
+
order_item.source_type = nil
|
|
19
|
+
order_item.source_id = nil
|
|
20
|
+
else
|
|
21
|
+
order_item.source_type = source_type
|
|
22
|
+
order_item.source_id = source_id
|
|
23
|
+
end
|
|
13
24
|
order_item.save!
|
|
14
25
|
order_item
|
|
15
26
|
end
|
data/config/routes.rb
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
class CreateDscfMarketplaceOtpVerifications < ActiveRecord::Migration[8.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :dscf_marketplace_otp_verifications do |t|
|
|
4
|
+
t.references :verifiable, polymorphic: true, null: false, index: false
|
|
5
|
+
t.string :phone, null: false
|
|
6
|
+
t.string :code, null: false
|
|
7
|
+
t.integer :status, default: 0, null: false
|
|
8
|
+
t.integer :attempts, default: 0, null: false
|
|
9
|
+
t.datetime :expires_at, null: false
|
|
10
|
+
t.datetime :verified_at
|
|
11
|
+
|
|
12
|
+
t.timestamps
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
add_index :dscf_marketplace_otp_verifications, %i[verifiable_type verifiable_id],
|
|
16
|
+
name: "verifiable_on_dm_otp_verifications_indx"
|
|
17
|
+
add_index :dscf_marketplace_otp_verifications, :status
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
FactoryBot.define do
|
|
2
|
+
factory :dscf_marketplace_otp_verification, class: "Dscf::Marketplace::OtpVerification" do
|
|
3
|
+
phone { Faker::PhoneNumber.phone_number }
|
|
4
|
+
code { Dscf::Marketplace::OtpVerification::DEFAULT_CODE }
|
|
5
|
+
expires_at { Dscf::Marketplace::OtpVerification::TTL.from_now }
|
|
6
|
+
association :verifiable, factory: :dscf_marketplace_order
|
|
7
|
+
end
|
|
8
|
+
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.
|
|
4
|
+
version: 0.13.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Asrat
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-07-
|
|
10
|
+
date: 2026-07-04 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: rails
|
|
@@ -459,6 +459,7 @@ files:
|
|
|
459
459
|
- app/models/dscf/marketplace/order.rb
|
|
460
460
|
- app/models/dscf/marketplace/order_invoice.rb
|
|
461
461
|
- app/models/dscf/marketplace/order_item.rb
|
|
462
|
+
- app/models/dscf/marketplace/otp_verification.rb
|
|
462
463
|
- app/models/dscf/marketplace/product.rb
|
|
463
464
|
- app/models/dscf/marketplace/product_inclusion_request.rb
|
|
464
465
|
- app/models/dscf/marketplace/quotation.rb
|
|
@@ -594,6 +595,8 @@ files:
|
|
|
594
595
|
- db/migrate/20260622000001_add_validation_and_source_fields_to_dscf_marketplace_order_items.rb
|
|
595
596
|
- db/migrate/20260702000001_unify_aggregator_listing_source.rb
|
|
596
597
|
- db/migrate/20260703000001_add_supplier_adjustment_to_dscf_marketplace_order_items.rb
|
|
598
|
+
- db/migrate/20260704000001_add_agent_id_to_dscf_marketplace_orders.rb
|
|
599
|
+
- db/migrate/20260704000002_create_dscf_marketplace_otp_verifications.rb
|
|
597
600
|
- db/seeds.rb
|
|
598
601
|
- lib/dscf/marketplace.rb
|
|
599
602
|
- lib/dscf/marketplace/engine.rb
|
|
@@ -617,6 +620,7 @@ files:
|
|
|
617
620
|
- spec/factories/dscf/marketplace/order_invoices.rb
|
|
618
621
|
- spec/factories/dscf/marketplace/order_items.rb
|
|
619
622
|
- spec/factories/dscf/marketplace/orders.rb
|
|
623
|
+
- spec/factories/dscf/marketplace/otp_verifications.rb
|
|
620
624
|
- spec/factories/dscf/marketplace/product_inclusion_requests.rb
|
|
621
625
|
- spec/factories/dscf/marketplace/products.rb
|
|
622
626
|
- spec/factories/dscf/marketplace/quotation_items.rb
|