dscf-marketplace 0.1.3 → 0.1.5

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: 796eeeaf3921a9ddb0e478285af2b644a972cb51a53231a39e7062e7c4b53f0a
4
- data.tar.gz: af123ae47644fd07cca78b7e3b4907ef79d530cf96dff069338499ebf908cd0e
3
+ metadata.gz: f6b077453f450386b96a2df3aac342c13fe4ec2bc97d4e34fa48b1e4918df3ed
4
+ data.tar.gz: 837589f60812fcf77b8a9958b344ec274458434ba63e695538746165349af8dd
5
5
  SHA512:
6
- metadata.gz: d6aa7f04ccb27eb52e027c68a72c429cfdf793d9c7dfde195851b868e6a69296e6328000eed13fd12075dd18ef76f164f6d7a8deee69a1dc31869f809c30da79
7
- data.tar.gz: 9a4653a2bbd5b67779a5180ea2dc660971e50485ed6224d86f7904079bdc1302bd6c26bb2c8006761c22f43b9e0f5e335c15db9bbfff8ed6098b83f51a5b9fdc
6
+ metadata.gz: f864dfe924f7002b4edc7686e3f227bfbf500ed63a1ecc67b3aa0d34fc76123d0b0d900495644da1552bd28539f7ec56db6fff44aa4c4e4320e8f72f741619a8
7
+ data.tar.gz: 5a5aae5e8036726d534492e592a984e2185eb8e4f4102596c7d918c61fb8c293e8b2da1c60954dbd200c5f88d709d3267fe9b523634938b91fb8137e8f162fc2
@@ -0,0 +1,55 @@
1
+ module Dscf::Marketplace
2
+ class Listing < ApplicationRecord
3
+ # References
4
+ belongs_to :business, class_name: "Dscf::Core::Business"
5
+ belongs_to :supplier_product, class_name: "Dscf::Marketplace::SupplierProduct"
6
+
7
+ # Relationships
8
+ has_many :order_items, class_name: "Dscf::Marketplace::OrderItem"
9
+
10
+ # Status enum
11
+ enum :status, {active: 0, draft: 1, paused: 2, sold_out: 3}, default: :draft
12
+
13
+ # Validations
14
+ validates :price, numericality: {greater_than: 0}, presence: true
15
+ validates :quantity, numericality: {greater_than: 0}, presence: true
16
+ validates :business_id, presence: true
17
+ validates :supplier_product_id, presence: true
18
+
19
+ # Scopes
20
+ scope :active, -> { where(status: :active) }
21
+ scope :by_business, ->(business_id) { where(business_id: business_id) }
22
+ scope :owned_by, ->(business) { where(business: business) }
23
+
24
+ # Custom methods
25
+ def total_value
26
+ price * quantity
27
+ end
28
+
29
+ def margin
30
+ return nil unless supplier_product&.supplier_price
31
+ price - supplier_product.supplier_price
32
+ end
33
+
34
+ def margin_percentage
35
+ return nil unless supplier_product&.supplier_price && supplier_product.supplier_price > 0
36
+ ((margin / supplier_product.supplier_price) * 100).round(2)
37
+ end
38
+
39
+ def available?
40
+ active? && quantity > 0
41
+ end
42
+
43
+ def supplier_business
44
+ supplier_product&.business
45
+ end
46
+
47
+ def product
48
+ supplier_product&.product
49
+ end
50
+
51
+ def can_be_managed_by?(business)
52
+ self.business == business
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,62 @@
1
+ module Dscf
2
+ module Marketplace
3
+ class Product < ApplicationRecord
4
+ belongs_to :category, class_name: "Dscf::Marketplace::Category"
5
+ belongs_to :unit, class_name: "Dscf::Marketplace::Unit"
6
+
7
+ has_many :supplier_products, class_name: "Dscf::Marketplace::SupplierProduct"
8
+ has_many :listings, class_name: "Dscf::Marketplace::Listing"
9
+ has_many :order_items, class_name: "Dscf::Marketplace::OrderItem"
10
+ has_many :rfq_items, class_name: "Dscf::Marketplace::RfqItem"
11
+ has_many :quotation_items, class_name: "Dscf::Marketplace::QuotationItem"
12
+
13
+ has_one_attached :thumbnail
14
+ has_many_attached :images
15
+
16
+ validates :sku, presence: true, uniqueness: true, length: {maximum: 50}
17
+ validates :name, presence: true, length: {maximum: 200}
18
+ validates :description, length: {maximum: 2000}, allow_blank: true
19
+ validates :category_id, presence: true
20
+ validates :base_price, presence: true, numericality: {greater_than: 0}
21
+ validates :business_only, inclusion: {in: [ true, false ]}
22
+ validates :unit_id, presence: true
23
+ validates :base_quantity, presence: true, numericality: {greater_than: 0}
24
+
25
+ scope :business_only, -> { where(business_only: true) }
26
+ scope :public_products, -> { where(business_only: false) }
27
+ scope :by_category, ->(category_id) { where(category_id: category_id) }
28
+ scope :by_unit, ->(unit_id) { where(unit_id: unit_id) }
29
+
30
+ def display_name
31
+ "#{name} (#{sku})"
32
+ end
33
+
34
+ def price_per_unit
35
+ return nil unless base_price && base_quantity && base_quantity > 0
36
+ base_price / base_quantity
37
+ end
38
+
39
+ def category_path
40
+ return nil unless category
41
+ category.ancestry_path
42
+ end
43
+
44
+ def convertible_to_units
45
+ return [] unless unit
46
+ unit.convertible_units
47
+ end
48
+
49
+ def thumbnail_url
50
+ return nil unless thumbnail.attached?
51
+ Rails.application.routes.url_helpers.rails_blob_url(thumbnail, only_path: true)
52
+ end
53
+
54
+ def images_urls
55
+ return [] unless images.attached?
56
+ images.map do |image|
57
+ Rails.application.routes.url_helpers.rails_blob_url(image, only_path: true)
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,47 @@
1
+ module Dscf::Marketplace
2
+ class SupplierProduct < ApplicationRecord
3
+ # References
4
+ belongs_to :business, class_name: "Dscf::Core::Business"
5
+ belongs_to :product, class_name: "Dscf::Marketplace::Product"
6
+
7
+ # Relationships
8
+ has_many :listings, class_name: "Dscf::Marketplace::Listing"
9
+ has_many :order_items, class_name: "Dscf::Marketplace::OrderItem"
10
+
11
+ # Status enum
12
+ enum :status, {active: 0, inactive: 1, discontinued: 2}, default: :active
13
+
14
+ # Validations
15
+ validates :supplier_price, numericality: {greater_than: 0}, presence: true
16
+ validates :available_quantity, numericality: {greater_than_or_equal_to: 0}
17
+ validates :minimum_order_quantity, numericality: {greater_than: 0}, allow_nil: true
18
+
19
+ # Scopes
20
+ scope :active, -> { where(status: :active) }
21
+ scope :in_stock, -> { where("available_quantity > 0") }
22
+ scope :by_business, ->(business_id) { where(business_id: business_id) }
23
+ scope :by_product, ->(product_id) { where(product_id: product_id) }
24
+
25
+ # Custom methods
26
+ def in_stock?
27
+ available_quantity > 0
28
+ end
29
+
30
+ def can_fulfill?(quantity)
31
+ available_quantity >= quantity
32
+ end
33
+
34
+ def price_per_unit
35
+ return nil unless supplier_price && product&.base_quantity
36
+ supplier_price / product.base_quantity
37
+ end
38
+
39
+ def display_name
40
+ "#{business.name} - #{product.name}"
41
+ end
42
+
43
+ def available_for_order?(quantity = 1)
44
+ active? && can_fulfill?(quantity)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,30 @@
1
+ class CreateDscfMarketplaceProducts < ActiveRecord::Migration[8.0]
2
+ def change
3
+ create_table :dscf_marketplace_products do |t|
4
+ t.string :sku, null: false, limit: 50
5
+ t.string :name, null: false, limit: 200
6
+ t.text :description, limit: 2000
7
+ t.references :category,
8
+ index: {name: "category_on_dm_products_indx"},
9
+ null: false,
10
+ foreign_key: {to_table: :dscf_marketplace_categories}
11
+ t.decimal :base_price, precision: 15, scale: 2, null: false
12
+ t.boolean :business_only, null: false, default: false
13
+ t.references :unit,
14
+ index: {name: "unit_on_dm_products_indx"},
15
+ null: false,
16
+ foreign_key: {to_table: :dscf_marketplace_units}
17
+ t.decimal :base_quantity, precision: 15, scale: 6, null: false
18
+
19
+ t.timestamps
20
+ end
21
+
22
+ add_index :dscf_marketplace_products, :sku,
23
+ unique: true,
24
+ name: "sku_on_dm_products_indx"
25
+ add_index :dscf_marketplace_products, :business_only,
26
+ name: "business_only_on_dm_products_indx"
27
+ add_index :dscf_marketplace_products, [ :category_id, :business_only ],
28
+ name: "category_business_only_on_dm_products_indx"
29
+ end
30
+ end
@@ -0,0 +1,32 @@
1
+ class CreateDscfMarketplaceSupplierProducts < ActiveRecord::Migration[8.0]
2
+ def change
3
+ create_table :dscf_marketplace_supplier_products do |t|
4
+ t.references :business,
5
+ index: {name: "business_on_dm_sp_indx"},
6
+ null: false,
7
+ foreign_key: {to_table: :dscf_core_businesses}
8
+ t.references :product,
9
+ index: {name: "product_on_dm_sp_indx"},
10
+ null: false,
11
+ foreign_key: {to_table: :dscf_marketplace_products}
12
+
13
+ # Ethiopian Birr pricing
14
+ t.decimal :supplier_price, precision: 15, scale: 2, null: false
15
+
16
+ # Inventory management
17
+ t.decimal :available_quantity, precision: 15, scale: 6, null: false, default: 0
18
+ t.decimal :minimum_order_quantity, precision: 15, scale: 6
19
+
20
+ # Status
21
+ t.integer :status, null: false, default: 0
22
+
23
+ t.timestamps
24
+ end
25
+
26
+ # Unique constraint - one supplier product per business-product combination
27
+ add_index :dscf_marketplace_supplier_products,
28
+ [ :business_id, :product_id ],
29
+ unique: true,
30
+ name: "unique_business_product_indx"
31
+ end
32
+ end
@@ -0,0 +1,31 @@
1
+ class CreateDscfMarketplaceListings < ActiveRecord::Migration[8.0]
2
+ def change
3
+ create_table :dscf_marketplace_listings do |t|
4
+ t.references :business,
5
+ index: {name: "business_on_dm_listings_indx"},
6
+ null: false,
7
+ foreign_key: {to_table: :dscf_core_businesses}
8
+ t.references :supplier_product,
9
+ index: {name: "supplier_product_on_dm_listings_indx"},
10
+ null: false,
11
+ foreign_key: {to_table: :dscf_marketplace_supplier_products}
12
+
13
+ # Ethiopian Birr pricing
14
+ t.decimal :price, precision: 15, scale: 2, null: false
15
+
16
+ # Available quantity for sale
17
+ t.decimal :quantity, precision: 15, scale: 6, null: false
18
+
19
+ # Optional listing description
20
+ t.text :description
21
+
22
+ # Status
23
+ t.integer :status, null: false, default: 1 # draft
24
+
25
+ t.timestamps
26
+ end
27
+
28
+ add_index :dscf_marketplace_listings, :status, name: "status_on_dm_listings_indx"
29
+ add_index :dscf_marketplace_listings, [ :business_id, :supplier_product_id ], name: "business_supplier_product_on_dm_listings_indx"
30
+ end
31
+ end
@@ -1,5 +1,5 @@
1
1
  module Dscf
2
2
  module Marketplace
3
- VERSION = "0.1.3".freeze
3
+ VERSION = "0.1.5".freeze
4
4
  end
5
5
  end
@@ -0,0 +1,5 @@
1
+ FactoryBot.define do
2
+ factory :dscf_core_business_type, class: 'Dscf::Core::BusinessType' do
3
+ sequence(:name) { |n| "Business Type #{n}" }
4
+ end
5
+ end
@@ -0,0 +1,11 @@
1
+ FactoryBot.define do
2
+ factory :dscf_core_business, class: 'Dscf::Core::Business' do
3
+ sequence(:name) { |n| "Test Business #{n}" }
4
+ description { "Test business description" }
5
+ contact_email { "contact@example.com" }
6
+ contact_phone { "+251911123456" }
7
+ tin_number { "1234567890" }
8
+ business_type { create(:dscf_core_business_type) }
9
+ user { create(:dscf_core_user) }
10
+ end
11
+ end
@@ -0,0 +1,8 @@
1
+ FactoryBot.define do
2
+ factory :dscf_core_user, class: 'Dscf::Core::User' do
3
+ sequence(:email) { |n| "user#{n}@example.com" }
4
+ sequence(:phone) { |n| "+25191112345#{n}" }
5
+ temp_password { false }
6
+ password_digest { "password_digest" }
7
+ end
8
+ end
@@ -0,0 +1,10 @@
1
+ FactoryBot.define do
2
+ factory :dscf_marketplace_listing, class: 'Dscf::Marketplace::Listing' do
3
+ business { create(:dscf_core_business) }
4
+ supplier_product { create(:dscf_marketplace_supplier_product) }
5
+ price { 500.00 }
6
+ quantity { 100.0 }
7
+ description { "High quality product listing" }
8
+ status { :draft }
9
+ end
10
+ end
@@ -0,0 +1,12 @@
1
+ FactoryBot.define do
2
+ factory :dscf_marketplace_product, class: 'Dscf::Marketplace::Product' do
3
+ sequence(:sku) { |n| "PRD-#{n.to_s.rjust(3, '0')}" }
4
+ sequence(:name) { |n| "Product #{n}" }
5
+ description { "Product description" }
6
+ category { create(:dscf_marketplace_category) }
7
+ base_price { 99.99 }
8
+ business_only { false }
9
+ unit { create(:dscf_marketplace_unit) }
10
+ base_quantity { 1.0 }
11
+ end
12
+ end
@@ -0,0 +1,10 @@
1
+ FactoryBot.define do
2
+ factory :dscf_marketplace_supplier_product, class: 'Dscf::Marketplace::SupplierProduct' do
3
+ business { create(:dscf_core_business) }
4
+ product { create(:dscf_marketplace_product) }
5
+ supplier_price { 500.00 }
6
+ available_quantity { 100.0 }
7
+ minimum_order_quantity { 10.0 }
8
+ status { :active }
9
+ end
10
+ 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.3
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Asrat
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-08-27 00:00:00.000000000 Z
10
+ date: 2025-08-28 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rails
@@ -422,6 +422,9 @@ files:
422
422
  - app/mailers/dscf/marketplace/application_mailer.rb
423
423
  - app/models/dscf/marketplace/application_record.rb
424
424
  - app/models/dscf/marketplace/category.rb
425
+ - app/models/dscf/marketplace/listing.rb
426
+ - app/models/dscf/marketplace/product.rb
427
+ - app/models/dscf/marketplace/supplier_product.rb
425
428
  - app/models/dscf/marketplace/unit.rb
426
429
  - app/models/dscf/marketplace/unit_conversion.rb
427
430
  - config/routes.rb
@@ -429,11 +432,20 @@ files:
429
432
  - db/migrate/20250827173526_update_dscf_marketplace_categories_indexes.rb
430
433
  - db/migrate/20250827182550_create_dscf_marketplace_units.rb
431
434
  - db/migrate/20250827185656_create_dscf_marketplace_unit_conversions.rb
435
+ - db/migrate/20250828062945_create_dscf_marketplace_products.rb
436
+ - db/migrate/20250828072514_create_dscf_marketplace_supplier_products.rb
437
+ - db/migrate/20250828081657_create_dscf_marketplace_listings.rb
432
438
  - lib/dscf/marketplace.rb
433
439
  - lib/dscf/marketplace/engine.rb
434
440
  - lib/dscf/marketplace/version.rb
435
441
  - lib/tasks/dscf/marketplace_tasks.rake
442
+ - spec/factories/dscf/core/business_types.rb
443
+ - spec/factories/dscf/core/businesses.rb
444
+ - spec/factories/dscf/core/users.rb
436
445
  - spec/factories/dscf/marketplace/categories.rb
446
+ - spec/factories/dscf/marketplace/listings.rb
447
+ - spec/factories/dscf/marketplace/products.rb
448
+ - spec/factories/dscf/marketplace/supplier_products.rb
437
449
  - spec/factories/dscf/marketplace/unit_conversions.rb
438
450
  - spec/factories/dscf/marketplace/units.rb
439
451
  homepage: https://mksaddis.com/