dscf-marketplace 0.13.10 → 0.13.11

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: 2ceef3327d3d9ce4e71a2b412bc95c909aef51e1fc86a3b9888035473825ffb5
4
- data.tar.gz: 5f326b8b1ead9bdf266ee04ac629961ebd3f9f4aa615c310058c70b48b2765a4
3
+ metadata.gz: 7dd1a186494d212b2e44fe094c3f31440f21764924868264316894e1152a7160
4
+ data.tar.gz: 03f4880b762fcf7ecebd26cc7368b07ee215b8445df4b61173b17a59dabca686
5
5
  SHA512:
6
- metadata.gz: afa2ef1d16856d57808dc61721c6d1c56384d5c5b9a1d58d81aa263c5dd2a60bcd1ef57c4f32d4e3d0107e79cc0046adb1d057ed3a491964eebe2c1670b79e21
7
- data.tar.gz: b8d4a631aa5917d4eb0cee9facd1daeaf8c8db6af9fd0b699268ca9f4a1d92293215d20466c4cd3778d1467b4f6f73c8884da6fd52c492db05e070a04c2dedd0
6
+ metadata.gz: 25a80f9a800788bb55dcfd6d44bf991b6bcf8ae25a11d4a5aa539b8db3154d0351107a990ca8844096d59a7bb4405979e52e4d8c8a0e1efc132dedbebfe2a412
7
+ data.tar.gz: 8efd86df8d48b449a15921f8acddd728b957e677840180111614d8680fb0c08c723b8533a110d7da13b5e126121eba73a9de5f18ff93ec49fd91864ef92221c5
@@ -5,7 +5,12 @@ module Dscf
5
5
 
6
6
  def feed
7
7
  authorize @clazz.new, :index?
8
- listings = @clazz.active.includes(:product, {supplier_product: :product}, {sub_supplier_product: :product})
8
+ # Retailer-facing feed: only listings that are truly offerable — own
9
+ # status :active AND (for supplier-sourced) the supplier hasn't paused
10
+ # or run out of stock. A listing whose supplier paused drops out here.
11
+ listings = @clazz.active
12
+ .includes(:product, {supplier_product: :product}, {sub_supplier_product: :product})
13
+ .select(&:available?)
9
14
 
10
15
  options = {
11
16
  include: default_serializer_includes[:index] || [],
@@ -0,0 +1,29 @@
1
+ module Dscf
2
+ module Marketplace
3
+ # Shared Active Storage URL helpers. Produces a host-aware proxy URL that
4
+ # works behind the API's `/api` script_name — unlike `only_path: true`,
5
+ # which drops the script_name and 404s when the client resolves the path.
6
+ module AttachmentUrls
7
+ extend ActiveSupport::Concern
8
+
9
+ private
10
+
11
+ def attachment_url_for(attachment)
12
+ return unless attachment.present?
13
+ return if attachment.respond_to?(:attached?) && !attachment.attached?
14
+
15
+ Rails.application.routes.url_helpers.rails_storage_proxy_url(attachment, **active_storage_url_options)
16
+ end
17
+
18
+ def active_storage_url_options
19
+ url_options = Rails.application.config.x.active_storage_url_options.presence ||
20
+ Rails.application.routes.default_url_options.presence ||
21
+ Rails.application.config.action_controller.default_url_options.presence ||
22
+ Rails.application.config.action_mailer.default_url_options.presence ||
23
+ {}
24
+
25
+ url_options.to_h.symbolize_keys.compact
26
+ end
27
+ end
28
+ end
29
+ end
@@ -1,5 +1,7 @@
1
1
  module Dscf::Marketplace
2
2
  class Agent < ApplicationRecord
3
+ include Dscf::Marketplace::AttachmentUrls
4
+
3
5
  # Associations
4
6
  belongs_to :user, class_name: "Dscf::Core::User", optional: true
5
7
  belongs_to :onboarded_by, class_name: "Dscf::Core::Business", optional: true
@@ -44,15 +46,11 @@ module Dscf::Marketplace
44
46
  end
45
47
 
46
48
  def photo_url
47
- return nil unless photo.attached?
48
-
49
- Rails.application.routes.url_helpers.rails_storage_proxy_url(photo, only_path: true)
49
+ attachment_url_for(photo)
50
50
  end
51
51
 
52
52
  def national_id_url
53
- return nil unless national_id.attached?
54
-
55
- Rails.application.routes.url_helpers.rails_storage_proxy_url(national_id, only_path: true)
53
+ attachment_url_for(national_id)
56
54
  end
57
55
 
58
56
  private
@@ -14,6 +14,12 @@ module Dscf::Marketplace
14
14
  # how the product/cost resolve, and who fulfils an order line placed against it.
15
15
  enum :source_kind, {catalog: 0, supplier: 1, sub_supplier: 2}, default: :catalog
16
16
 
17
+ # At listing time the quantity for supplier/sub_supplier sources is the
18
+ # supplier's own maintained stock — the aggregator never types it in. Seed it
19
+ # from upstream on create; afterwards it's decremented by order fulfilment
20
+ # like any listing, so we don't re-sync on update.
21
+ before_validation :sync_quantity_from_source, on: :create
22
+
17
23
  validates :price, numericality: {greater_than: 0}, presence: true
18
24
  # Quantity must be positive while the listing is orderable, but a sold-out
19
25
  # listing legitimately holds 0 — that's exactly the state set when the last
@@ -86,12 +92,44 @@ module Dscf::Marketplace
86
92
  supplier_product&.business if supplier?
87
93
  end
88
94
 
95
+ # Is the upstream source still offering this? For supplier/sub_supplier
96
+ # sources, an aggregator listing must follow the supplier: when the supplier
97
+ # pauses (deactivates their supplier product) or runs out of stock, the
98
+ # listing is no longer offerable even if its own status is still :active.
99
+ # Catalog listings have no upstream — the aggregator fulfils them directly.
100
+ def upstream_active?
101
+ case source_kind
102
+ when "supplier" then supplier_product&.active? && supplier_product.available_quantity.to_i.positive?
103
+ when "sub_supplier" then sub_supplier_product.present?
104
+ else true
105
+ end
106
+ end
107
+
108
+ # Status as retailers/aggregator should see it: a listing whose own status is
109
+ # :active but whose supplier has paused reads as :paused (auto-followed),
110
+ # without mutating the stored status.
111
+ def effective_status
112
+ return "paused" if active? && !upstream_active?
113
+
114
+ status
115
+ end
116
+
89
117
  def available?
90
- active? && quantity > 0
118
+ active? && quantity.to_i.positive? && upstream_active?
91
119
  end
92
120
 
93
121
  def total_value
94
122
  price * quantity
95
123
  end
124
+
125
+ private
126
+
127
+ def sync_quantity_from_source
128
+ case source_kind
129
+ when "supplier" then self.quantity = supplier_product&.available_quantity
130
+ when "sub_supplier" then self.quantity = sub_supplier_product&.quantity
131
+ # catalog: aggregator sets its own quantity
132
+ end
133
+ end
96
134
  end
97
135
  end
@@ -1,6 +1,8 @@
1
1
  module Dscf
2
2
  module Marketplace
3
3
  class Product < ApplicationRecord
4
+ include Dscf::Marketplace::AttachmentUrls
5
+
4
6
  belongs_to :category, class_name: "Dscf::Marketplace::Category"
5
7
  belongs_to :unit, class_name: "Dscf::Marketplace::Unit"
6
8
 
@@ -65,23 +67,6 @@ module Dscf
65
67
  images.map { |image| attachment_url_for(image) }
66
68
  end
67
69
 
68
- private
69
- def attachment_url_for(attachment)
70
- return unless attachment.present?
71
- return if attachment.respond_to?(:attached?) && !attachment.attached?
72
-
73
- Rails.application.routes.url_helpers.rails_storage_proxy_url(attachment, **active_storage_url_options)
74
- end
75
-
76
- def active_storage_url_options
77
- url_options = Rails.application.config.x.active_storage_url_options.presence ||
78
- Rails.application.routes.default_url_options.presence ||
79
- Rails.application.config.action_controller.default_url_options.presence ||
80
- Rails.application.config.action_mailer.default_url_options.presence ||
81
- {}
82
-
83
- url_options.to_h.symbolize_keys.compact
84
- end
85
70
  end
86
71
  end
87
72
  end
@@ -1,6 +1,7 @@
1
1
  module Dscf::Marketplace
2
2
  class Supplier < ApplicationRecord
3
3
  include Dscf::Core::ReviewableModel
4
+ include Dscf::Marketplace::AttachmentUrls
4
5
 
5
6
  belongs_to :business, class_name: "Dscf::Core::Business"
6
7
  has_many :supplier_products, class_name: "Dscf::Marketplace::SupplierProduct",
@@ -92,9 +93,7 @@ module Dscf::Marketplace
92
93
  def document_urls
93
94
  return [] unless documents.attached?
94
95
 
95
- documents.map do |doc|
96
- Rails.application.routes.url_helpers.rails_storage_proxy_url(doc, only_path: true)
97
- end
96
+ documents.map { |doc| attachment_url_for(doc) }
98
97
  end
99
98
 
100
99
  private
@@ -3,7 +3,7 @@ module Dscf
3
3
  class AggregatorListingSerializer < ActiveModel::Serializer
4
4
  attributes :id, :aggregator_id, :source_kind, :supplier_product_id, :product_id,
5
5
  :sub_supplier_product_id, :price, :quantity, :cost_price, :margin,
6
- :status, :created_at, :updated_at, :total_value, :available?,
6
+ :status, :effective_status, :created_at, :updated_at, :total_value, :available?,
7
7
  :product_name, :product_description, :thumbnail_url, :images_urls,
8
8
  :source_label
9
9
 
@@ -1,5 +1,5 @@
1
1
  module Dscf
2
2
  module Marketplace
3
- VERSION = "0.13.10".freeze
3
+ VERSION = "0.13.11".freeze
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dscf-marketplace
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.10
4
+ version: 0.13.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Asrat
@@ -446,6 +446,7 @@ files:
446
446
  - app/controllers/dscf/marketplace/units_controller.rb
447
447
  - app/jobs/dscf/marketplace/application_job.rb
448
448
  - app/mailers/dscf/marketplace/application_mailer.rb
449
+ - app/models/concerns/dscf/marketplace/attachment_urls.rb
449
450
  - app/models/dscf/marketplace/agent.rb
450
451
  - app/models/dscf/marketplace/aggregator_listing.rb
451
452
  - app/models/dscf/marketplace/application_record.rb