dscf-marketplace 0.1.6 → 0.1.7
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/models/dscf/marketplace/order.rb +105 -0
- data/app/models/dscf/marketplace/order_item.rb +30 -0
- data/app/models/dscf/marketplace/quotation.rb +37 -0
- data/db/migrate/20250828115509_create_dscf_marketplace_orders.rb +19 -0
- data/db/migrate/20250828115525_create_dscf_marketplace_order_items.rb +22 -0
- data/lib/dscf/marketplace/version.rb +1 -1
- data/spec/factories/dscf/marketplace/listings.rb +16 -4
- data/spec/factories/dscf/marketplace/order_items.rb +49 -0
- data/spec/factories/dscf/marketplace/orders.rb +36 -0
- data/spec/factories/dscf/marketplace/{dscf/marketplace/quotation_items.rb → quotation_items.rb} +1 -1
- data/spec/factories/dscf/marketplace/{dscf/marketplace/quotations.rb → quotations.rb} +4 -4
- metadata +12 -6
- /data/spec/factories/dscf/marketplace/{dscf/marketplace/request_for_quotations.rb → request_for_quotations.rb} +0 -0
- /data/spec/factories/dscf/marketplace/{dscf/marketplace/rfq_items.rb → rfq_items.rb} +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 60c2cbf7461bd253837b481338ec8105fc47e5ff3927855341efc9261bf193f5
|
4
|
+
data.tar.gz: 3d10a078bab90804d176823e1cf0e44ed74f617ca3f0adeff657339438404642
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 96051d9c835dc17d0f8c7b495c28dd48b2b45a9690711be8d9a5650f6f36643498605d4d8bfdeaf6df6a7cea2f04567b6aff8479a444c525fb68ddbc60ebf41b
|
7
|
+
data.tar.gz: 6e35c21f3b43d962d838b1f5bbfb577db65e2c004c0665e340356994a93fb1e89d594d02358209af2670a6ea8638fc909347f80a8cbdd470e40d975520433af1
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module Dscf::Marketplace
|
2
|
+
class Order < ApplicationRecord
|
3
|
+
enum :order_type, {rfq_based: 0, direct_listing: 1}
|
4
|
+
enum :status, {pending: 0, confirmed: 1, processing: 2, completed: 3, cancelled: 4}
|
5
|
+
|
6
|
+
belongs_to :quotation, optional: true
|
7
|
+
belongs_to :listing, optional: true
|
8
|
+
belongs_to :user, class_name: "Dscf::Core::User"
|
9
|
+
has_many :order_items, dependent: :destroy
|
10
|
+
|
11
|
+
validates :order_type, presence: true
|
12
|
+
validates :status, presence: true
|
13
|
+
validates :user, presence: true
|
14
|
+
validate :quotation_or_listing_present
|
15
|
+
|
16
|
+
before_save :calculate_total_amount
|
17
|
+
|
18
|
+
def self.create_from_quotation(quotation)
|
19
|
+
return nil unless quotation.accepted?
|
20
|
+
|
21
|
+
order = create!(
|
22
|
+
order_type: :rfq_based,
|
23
|
+
status: :pending,
|
24
|
+
quotation: quotation,
|
25
|
+
user: quotation.request_for_quotation.user,
|
26
|
+
total_amount: quotation.total_price
|
27
|
+
)
|
28
|
+
|
29
|
+
quotation.quotation_items.each do |item|
|
30
|
+
order.order_items.create!(
|
31
|
+
quotation_item: item,
|
32
|
+
product: item.product,
|
33
|
+
unit: item.unit,
|
34
|
+
quantity: item.quantity,
|
35
|
+
unit_price: item.unit_price,
|
36
|
+
status: :pending
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
order
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.create_from_listing(listing, user, quantity)
|
44
|
+
return nil unless listing.status == "active" && quantity <= listing.quantity
|
45
|
+
|
46
|
+
order = create!(
|
47
|
+
order_type: :direct_listing,
|
48
|
+
status: :pending,
|
49
|
+
listing: listing,
|
50
|
+
user: user,
|
51
|
+
total_amount: listing.price * quantity
|
52
|
+
)
|
53
|
+
|
54
|
+
order.order_items.create!(
|
55
|
+
listing: listing,
|
56
|
+
product: listing.supplier_product.product,
|
57
|
+
unit: listing.supplier_product.product.unit,
|
58
|
+
quantity: quantity,
|
59
|
+
unit_price: listing.price,
|
60
|
+
status: :pending
|
61
|
+
)
|
62
|
+
|
63
|
+
order
|
64
|
+
end
|
65
|
+
|
66
|
+
def total_amount
|
67
|
+
# Return stored value if it exists and is greater than 0, otherwise calculate
|
68
|
+
stored_value = super
|
69
|
+
calculated_value = order_items.sum { |item| item.quantity * item.unit_price }
|
70
|
+
|
71
|
+
# Return stored value if it's set and valid, otherwise return calculated value
|
72
|
+
return stored_value if stored_value.present? && stored_value > 0
|
73
|
+
calculated_value
|
74
|
+
end
|
75
|
+
|
76
|
+
def supplier
|
77
|
+
if rfq_based?
|
78
|
+
quotation&.business
|
79
|
+
elsif direct_listing?
|
80
|
+
listing&.business
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def confirm!
|
85
|
+
return false unless pending?
|
86
|
+
|
87
|
+
update!(status: :confirmed)
|
88
|
+
# Update order items without reloading to avoid association issues
|
89
|
+
order_items.update_all(status: OrderItem.statuses[:confirmed])
|
90
|
+
true
|
91
|
+
end
|
92
|
+
|
93
|
+
def calculate_total_amount
|
94
|
+
self.total_amount = order_items.sum { |item| item.quantity * item.unit_price }
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def quotation_or_listing_present
|
100
|
+
unless quotation.present? || listing.present?
|
101
|
+
errors.add(:base, "Either quotation or listing must be present")
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Dscf::Marketplace
|
2
|
+
class OrderItem < ApplicationRecord
|
3
|
+
enum :status, {pending: 0, confirmed: 1, processing: 2, fulfilled: 3, cancelled: 4}
|
4
|
+
|
5
|
+
belongs_to :order, optional: true
|
6
|
+
belongs_to :quotation_item, optional: true
|
7
|
+
belongs_to :listing, optional: true
|
8
|
+
belongs_to :product
|
9
|
+
belongs_to :unit
|
10
|
+
|
11
|
+
validates :quantity, presence: true, numericality: {greater_than: 0}
|
12
|
+
validates :unit_price, presence: true, numericality: {greater_than_or_equal_to: 0}
|
13
|
+
validates :status, presence: true
|
14
|
+
validates :product, presence: true
|
15
|
+
validates :unit, presence: true
|
16
|
+
validate :quotation_item_or_listing_present
|
17
|
+
|
18
|
+
def subtotal
|
19
|
+
quantity * unit_price
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def quotation_item_or_listing_present
|
25
|
+
unless quotation_item.present? || listing.present?
|
26
|
+
errors.add(:base, "Either quotation_item or listing must be present")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -5,6 +5,7 @@ module Dscf
|
|
5
5
|
belongs_to :business, class_name: "Dscf::Core::Business", optional: true if defined?(Dscf::Core)
|
6
6
|
|
7
7
|
has_many :quotation_items, class_name: "Dscf::Marketplace::QuotationItem", dependent: :destroy
|
8
|
+
has_one :order, class_name: "Dscf::Marketplace::Order", dependent: :destroy
|
8
9
|
|
9
10
|
validates :request_for_quotation_id, presence: true
|
10
11
|
validates :business_id, presence: true
|
@@ -79,9 +80,19 @@ module Dscf
|
|
79
80
|
|
80
81
|
update_columns(status: "accepted")
|
81
82
|
request_for_quotation.update!(status: "selected", selected_quotation: self)
|
83
|
+
|
84
|
+
# Create order from accepted quotation
|
85
|
+
create_order_from_quotation
|
86
|
+
|
82
87
|
true
|
83
88
|
end
|
84
89
|
|
90
|
+
def create_order_from_quotation
|
91
|
+
return if order.present?
|
92
|
+
|
93
|
+
Order.create_from_quotation(self)
|
94
|
+
end
|
95
|
+
|
85
96
|
def reject!
|
86
97
|
return false unless sent?
|
87
98
|
|
@@ -113,6 +124,32 @@ module Dscf
|
|
113
124
|
quotation_items.exists? && quotation_items.all? { |item| item.unit_price.present? }
|
114
125
|
end
|
115
126
|
|
127
|
+
def create_order_from_quotation
|
128
|
+
return if order.present?
|
129
|
+
|
130
|
+
order = Dscf::Marketplace::Order.create!(
|
131
|
+
order_type: :rfq_based,
|
132
|
+
status: :pending,
|
133
|
+
quotation: self,
|
134
|
+
user: request_for_quotation.user,
|
135
|
+
total_amount: total_price
|
136
|
+
)
|
137
|
+
|
138
|
+
quotation_items.each do |item|
|
139
|
+
Dscf::Marketplace::OrderItem.create!(
|
140
|
+
order: order,
|
141
|
+
quotation_item: item,
|
142
|
+
product: item.product,
|
143
|
+
unit: item.unit,
|
144
|
+
quantity: item.quantity,
|
145
|
+
unit_price: item.unit_price,
|
146
|
+
status: :pending
|
147
|
+
)
|
148
|
+
end
|
149
|
+
|
150
|
+
order
|
151
|
+
end
|
152
|
+
|
116
153
|
private
|
117
154
|
|
118
155
|
def calculate_total_price
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class CreateDscfMarketplaceOrders < ActiveRecord::Migration[8.0]
|
2
|
+
def change
|
3
|
+
create_table :dscf_marketplace_orders do |t|
|
4
|
+
t.integer :order_type
|
5
|
+
t.integer :status
|
6
|
+
t.references :quotation, null: true, foreign_key: {to_table: :dscf_marketplace_quotations}
|
7
|
+
t.references :listing, null: true, foreign_key: {to_table: :dscf_marketplace_listings}
|
8
|
+
t.references :user, null: false, foreign_key: {to_table: :dscf_core_users}
|
9
|
+
t.decimal :total_amount
|
10
|
+
|
11
|
+
t.timestamps
|
12
|
+
end
|
13
|
+
|
14
|
+
add_index :dscf_marketplace_orders, :user_id, name: "user_id_on_dm_orders_idx"
|
15
|
+
add_index :dscf_marketplace_orders, :quotation_id, name: "quotation_id_on_dm_orders_idx"
|
16
|
+
add_index :dscf_marketplace_orders, :listing_id, name: "listing_id_on_dm_orders_idx"
|
17
|
+
add_index :dscf_marketplace_orders, :status, name: "status_on_dm_orders_idx"
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class CreateDscfMarketplaceOrderItems < ActiveRecord::Migration[8.0]
|
2
|
+
def change
|
3
|
+
create_table :dscf_marketplace_order_items do |t|
|
4
|
+
t.references :order, null: false, foreign_key: {to_table: :dscf_marketplace_orders}
|
5
|
+
t.references :quotation_item, null: true, foreign_key: {to_table: :dscf_marketplace_quotation_items}
|
6
|
+
t.references :listing, null: true, foreign_key: {to_table: :dscf_marketplace_listings}
|
7
|
+
t.references :product, null: false, foreign_key: {to_table: :dscf_marketplace_products}
|
8
|
+
t.references :unit, null: false, foreign_key: {to_table: :dscf_marketplace_units}
|
9
|
+
t.decimal :quantity, precision: 15, scale: 6
|
10
|
+
t.decimal :unit_price
|
11
|
+
t.integer :status
|
12
|
+
|
13
|
+
t.timestamps
|
14
|
+
end
|
15
|
+
|
16
|
+
add_index :dscf_marketplace_order_items, :order_id, name: "order_id_on_dm_order_items_idx"
|
17
|
+
add_index :dscf_marketplace_order_items, :quotation_item_id, name: "quotation_item_id_on_dm_order_items_idx"
|
18
|
+
add_index :dscf_marketplace_order_items, :listing_id, name: "listing_id_on_dm_order_items_idx"
|
19
|
+
add_index :dscf_marketplace_order_items, :product_id, name: "product_id_on_dm_order_items_idx"
|
20
|
+
add_index :dscf_marketplace_order_items, :unit_id, name: "unit_id_on_dm_order_items_idx"
|
21
|
+
end
|
22
|
+
end
|
@@ -1,10 +1,22 @@
|
|
1
1
|
FactoryBot.define do
|
2
|
-
factory :dscf_marketplace_listing, class:
|
3
|
-
business
|
4
|
-
supplier_product
|
2
|
+
factory :dscf_marketplace_listing, class: "Dscf::Marketplace::Listing" do
|
3
|
+
association :business, factory: :dscf_core_business
|
4
|
+
association :supplier_product, factory: :dscf_marketplace_supplier_product
|
5
5
|
price { 500.00 }
|
6
6
|
quantity { 100.0 }
|
7
7
|
description { "High quality product listing" }
|
8
|
-
status
|
8
|
+
# status defaults to :draft from model
|
9
|
+
|
10
|
+
trait :active do
|
11
|
+
status { :active }
|
12
|
+
end
|
13
|
+
|
14
|
+
trait :draft do
|
15
|
+
status { :draft }
|
16
|
+
end
|
17
|
+
|
18
|
+
trait :paused do
|
19
|
+
status { :paused }
|
20
|
+
end
|
9
21
|
end
|
10
22
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
FactoryBot.define do
|
2
|
+
factory :dscf_marketplace_order_item, class: "Dscf::Marketplace::OrderItem" do
|
3
|
+
# Don't create order by default, let tests specify
|
4
|
+
quantity { 5 }
|
5
|
+
unit_price { 10.0 }
|
6
|
+
status { :pending }
|
7
|
+
quotation_item { nil } # Don't create by default
|
8
|
+
listing { nil } # Don't create by default
|
9
|
+
|
10
|
+
# Set product and unit based on quotation_item or listing
|
11
|
+
after(:build) do |item|
|
12
|
+
if item.quotation_item
|
13
|
+
item.product ||= item.quotation_item.product
|
14
|
+
item.unit ||= item.quotation_item.unit
|
15
|
+
elsif item.listing
|
16
|
+
item.product ||= item.listing.supplier_product.product
|
17
|
+
item.unit ||= item.listing.supplier_product.product.unit
|
18
|
+
else
|
19
|
+
item.product ||= create(:dscf_marketplace_product)
|
20
|
+
item.unit ||= create(:dscf_marketplace_unit)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
trait :from_quotation do
|
25
|
+
association :quotation_item, factory: :dscf_marketplace_quotation_item
|
26
|
+
end
|
27
|
+
|
28
|
+
trait :from_listing do
|
29
|
+
association :listing, factory: :dscf_marketplace_listing
|
30
|
+
quotation_item { nil }
|
31
|
+
end
|
32
|
+
|
33
|
+
trait :confirmed do
|
34
|
+
status { :confirmed }
|
35
|
+
end
|
36
|
+
|
37
|
+
trait :processing do
|
38
|
+
status { :processing }
|
39
|
+
end
|
40
|
+
|
41
|
+
trait :fulfilled do
|
42
|
+
status { :fulfilled }
|
43
|
+
end
|
44
|
+
|
45
|
+
trait :cancelled do
|
46
|
+
status { :cancelled }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
FactoryBot.define do
|
2
|
+
factory :dscf_marketplace_order, class: "Dscf::Marketplace::Order" do
|
3
|
+
order_type { :rfq_based }
|
4
|
+
status { :pending }
|
5
|
+
association :user, factory: :dscf_core_user
|
6
|
+
quotation { nil }
|
7
|
+
listing { nil }
|
8
|
+
|
9
|
+
trait :rfq_based do
|
10
|
+
order_type { :rfq_based }
|
11
|
+
association :quotation, factory: :dscf_marketplace_quotation
|
12
|
+
end
|
13
|
+
|
14
|
+
trait :direct_listing do
|
15
|
+
order_type { :direct_listing }
|
16
|
+
association :listing, factory: :dscf_marketplace_listing
|
17
|
+
quotation { nil }
|
18
|
+
end
|
19
|
+
|
20
|
+
trait :confirmed do
|
21
|
+
status { :confirmed }
|
22
|
+
end
|
23
|
+
|
24
|
+
trait :processing do
|
25
|
+
status { :processing }
|
26
|
+
end
|
27
|
+
|
28
|
+
trait :completed do
|
29
|
+
status { :completed }
|
30
|
+
end
|
31
|
+
|
32
|
+
trait :cancelled do
|
33
|
+
status { :cancelled }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/spec/factories/dscf/marketplace/{dscf/marketplace/quotation_items.rb → quotation_items.rb}
RENAMED
@@ -1,5 +1,5 @@
|
|
1
1
|
FactoryBot.define do
|
2
|
-
factory :dscf_marketplace_quotation_item, class:
|
2
|
+
factory :dscf_marketplace_quotation_item, class: "Dscf::Marketplace::QuotationItem" do
|
3
3
|
association :quotation, factory: :dscf_marketplace_quotation
|
4
4
|
association :rfq_item, factory: :dscf_marketplace_rfq_item
|
5
5
|
product { rfq_item.product }
|
@@ -1,11 +1,11 @@
|
|
1
1
|
FactoryBot.define do
|
2
|
-
factory :dscf_marketplace_quotation, class:
|
2
|
+
factory :dscf_marketplace_quotation, class: "Dscf::Marketplace::Quotation" do
|
3
3
|
association :request_for_quotation, factory: :dscf_marketplace_request_for_quotation
|
4
4
|
association :business, factory: :dscf_core_business
|
5
5
|
total_price { nil }
|
6
6
|
delivery_date { 5.days.from_now.to_date }
|
7
7
|
valid_until { 15.days.from_now }
|
8
|
-
status {
|
8
|
+
status { :draft }
|
9
9
|
notes { "Sample quotation notes" }
|
10
10
|
|
11
11
|
trait :expired do
|
@@ -15,11 +15,11 @@ FactoryBot.define do
|
|
15
15
|
end
|
16
16
|
|
17
17
|
trait :sent do
|
18
|
-
status {
|
18
|
+
status { :sent }
|
19
19
|
end
|
20
20
|
|
21
21
|
trait :accepted do
|
22
|
-
status {
|
22
|
+
status { :accepted }
|
23
23
|
end
|
24
24
|
end
|
25
25
|
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.1.
|
4
|
+
version: 0.1.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Asrat
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-08-
|
10
|
+
date: 2025-08-31 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: rails
|
@@ -423,6 +423,8 @@ files:
|
|
423
423
|
- app/models/dscf/marketplace/application_record.rb
|
424
424
|
- app/models/dscf/marketplace/category.rb
|
425
425
|
- app/models/dscf/marketplace/listing.rb
|
426
|
+
- app/models/dscf/marketplace/order.rb
|
427
|
+
- app/models/dscf/marketplace/order_item.rb
|
426
428
|
- app/models/dscf/marketplace/product.rb
|
427
429
|
- app/models/dscf/marketplace/quotation.rb
|
428
430
|
- app/models/dscf/marketplace/quotation_item.rb
|
@@ -445,6 +447,8 @@ files:
|
|
445
447
|
- db/migrate/20250828090300_create_dscf_marketplace_quotation_items.rb
|
446
448
|
- db/migrate/20250828090400_change_rfq_status_to_integer.rb
|
447
449
|
- db/migrate/20250828090500_change_quotation_status_to_integer.rb
|
450
|
+
- db/migrate/20250828115509_create_dscf_marketplace_orders.rb
|
451
|
+
- db/migrate/20250828115525_create_dscf_marketplace_order_items.rb
|
448
452
|
- lib/dscf/marketplace.rb
|
449
453
|
- lib/dscf/marketplace/engine.rb
|
450
454
|
- lib/dscf/marketplace/version.rb
|
@@ -453,12 +457,14 @@ files:
|
|
453
457
|
- spec/factories/dscf/core/businesses.rb
|
454
458
|
- spec/factories/dscf/core/users.rb
|
455
459
|
- spec/factories/dscf/marketplace/categories.rb
|
456
|
-
- spec/factories/dscf/marketplace/dscf/marketplace/quotation_items.rb
|
457
|
-
- spec/factories/dscf/marketplace/dscf/marketplace/quotations.rb
|
458
|
-
- spec/factories/dscf/marketplace/dscf/marketplace/request_for_quotations.rb
|
459
|
-
- spec/factories/dscf/marketplace/dscf/marketplace/rfq_items.rb
|
460
460
|
- spec/factories/dscf/marketplace/listings.rb
|
461
|
+
- spec/factories/dscf/marketplace/order_items.rb
|
462
|
+
- spec/factories/dscf/marketplace/orders.rb
|
461
463
|
- spec/factories/dscf/marketplace/products.rb
|
464
|
+
- spec/factories/dscf/marketplace/quotation_items.rb
|
465
|
+
- spec/factories/dscf/marketplace/quotations.rb
|
466
|
+
- spec/factories/dscf/marketplace/request_for_quotations.rb
|
467
|
+
- spec/factories/dscf/marketplace/rfq_items.rb
|
462
468
|
- spec/factories/dscf/marketplace/supplier_products.rb
|
463
469
|
- spec/factories/dscf/marketplace/unit_conversions.rb
|
464
470
|
- spec/factories/dscf/marketplace/units.rb
|
File without changes
|
File without changes
|