dscf-marketplace 0.13.3 → 0.13.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: 475043853266506246b2775c97fc0fb8a4c361fb7e415ae36b0c30c0a36f6a34
4
- data.tar.gz: 5e5045ee415293974c10856a416907a1aeb9434a9e51fed26aecf72ed4ef1339
3
+ metadata.gz: ac67a4d6ccac52aad74574a62d841183717112bad1c093526a9b9966421a4747
4
+ data.tar.gz: e11523f30c6f601d8bad9f14c271a8077354d79e21c3a23bb991779f8c8f8cf7
5
5
  SHA512:
6
- metadata.gz: 19eb9d280c9b18f8cf07c1532acff967a2bee651bc930f2c24a5142686999308b176d1622ebdbe9be233662fac3ed99bb4f55f2e50b4e3f4ac3bd29bc2e516ff
7
- data.tar.gz: a907e0a7fa9aba5f8c14b3eaabc4b47224278bfdc32d758300c20744d12747a546aa846a219386f49e1cc6855fd96adebab43274f6835181fb016bcfbb5e0841
6
+ metadata.gz: 3468066de76f24decf7c547c0eb3b28044607082b64163251d667f3f8754adad9e47d2a0dfb99f91d93dd07c0595ad01e7dc896d77d222b9b4c25cfcf3598188
7
+ data.tar.gz: 88a186edeb669c7d90fda622a7c5a4835d4479f1d80064d926cbd396ed09c800efb3da3cb34ddd3e67b0269cc81ef6bc8d7454456b6cc5b5e3bf3805d8df48f4
@@ -534,13 +534,15 @@ module Dscf
534
534
  else
535
535
  aggregator_listing = ri[:aggregator_listing]
536
536
  product = aggregator_listing.product
537
- # Order lines from the aggregator feed are auto-sourced to the originating
538
- # AggregatorListing (regardless of source_kind) the aggregator already
539
- # knows the source, so there's no manual pick at split time. The listing's
540
- # source_kind carries who ultimately fulfils it (self / sub-supplier /
541
- # supplier), surfaced via source_name for display and confirmation routing.
537
+ # Order lines from the aggregator feed are auto-sourced so there's
538
+ # no manual pick at split time. A :supplier-kind listing has a real
539
+ # external business behind it source the line straight to that
540
+ # Business so it shows up on the supplier's own confirmations page
541
+ # (same as if the aggregator had manually assigned it there).
542
+ # :catalog / :sub_supplier stay sourced to the AggregatorListing
543
+ # itself (aggregator-managed — see AggregatorListing#confirming_business).
542
544
  order.order_items.build(
543
- source: aggregator_listing,
545
+ source: aggregator_listing.confirming_business || aggregator_listing,
544
546
  product: product,
545
547
  unit: product.unit,
546
548
  quantity: ri[:quantity],
@@ -15,7 +15,11 @@ module Dscf::Marketplace
15
15
  enum :source_kind, {catalog: 0, supplier: 1, sub_supplier: 2}, default: :catalog
16
16
 
17
17
  validates :price, numericality: {greater_than: 0}, presence: true
18
- validates :quantity, numericality: {greater_than: 0}, presence: true
18
+ # Quantity must be positive while the listing is orderable, but a sold-out
19
+ # listing legitimately holds 0 — that's exactly the state set when the last
20
+ # unit is purchased (order creation decrements to 0 and flips to :sold_out).
21
+ validates :quantity, presence: true, numericality: {greater_than_or_equal_to: 0}
22
+ validates :quantity, numericality: {greater_than: 0}, unless: :sold_out?
19
23
  validates :aggregator, presence: true
20
24
 
21
25
  # Each kind requires (and is unique on) exactly one reference.
@@ -71,6 +75,17 @@ module Dscf::Marketplace
71
75
  end
72
76
  end
73
77
 
78
+ # The real external business that should confirm an order line placed
79
+ # against this listing on their OWN business dashboard, if any. Only
80
+ # meaningful for :supplier — that business is a genuine third party,
81
+ # unambiguously distinct from the aggregator. :catalog has no external
82
+ # party (the aggregator fulfils it) and :sub_supplier's business is
83
+ # offline/has no login (requirements doc: sub-suppliers "don't act
84
+ # directly on the system"), so both stay aggregator-managed (nil here).
85
+ def confirming_business
86
+ supplier_product&.business if supplier?
87
+ end
88
+
74
89
  def available?
75
90
  active? && quantity > 0
76
91
  end
@@ -14,7 +14,11 @@ module Dscf::Marketplace
14
14
 
15
15
  # Validations
16
16
  validates :price, numericality: {greater_than: 0}, presence: true
17
- validates :quantity, numericality: {greater_than: 0}, presence: true
17
+ # Quantity must be positive while the listing is orderable, but a sold-out
18
+ # listing legitimately holds 0 — that's exactly the state set when the last
19
+ # unit is purchased (order creation decrements to 0 and flips to :sold_out).
20
+ validates :quantity, presence: true, numericality: {greater_than_or_equal_to: 0}
21
+ validates :quantity, numericality: {greater_than: 0}, unless: :sold_out?
18
22
  validates :business_id, presence: true
19
23
  validates :supplier_product_id, presence: true
20
24
 
@@ -275,7 +275,14 @@ module Dscf::Marketplace
275
275
  end
276
276
 
277
277
  def quotation_or_listing_present
278
- unless quotation.present? || listing.present? || order_items.any? { |item| item.source_type == "Dscf::Marketplace::AggregatorListing" }
278
+ # A feed-purchased order has no order.listing (Order#listing is a single
279
+ # FK; nothing to point it at when every item came from the aggregator's
280
+ # feed) but each item's own presence validation already requires it to
281
+ # carry a quotation_item, listing, or assigned source — so any item with
282
+ # an assigned source_type proves the order is legitimately grounded,
283
+ # regardless of whether that source resolved to an AggregatorListing or
284
+ # straight to the fulfilling Business (see AggregatorListing#confirming_business).
285
+ unless quotation.present? || listing.present? || order_items.any? { |item| item.source_type.present? }
279
286
  errors.add(:base, "Either quotation or listing must be present")
280
287
  end
281
288
  end
@@ -57,17 +57,28 @@ module Dscf
57
57
  # any object responding to #price and #quantity works (Listing or
58
58
  # AggregatorListing), since validate_item only reads those two fields.
59
59
  private_class_method def self.find_current_active_listing(item)
60
- # Lines placed from the aggregator feed are sourced to their AggregatorListing —
61
- # validate against that listing's live price/quantity, not a supplier listing.
62
- if item.source.is_a?(AggregatorListing)
63
- return AggregatorListing.active.find_by(id: item.source_id)
64
- end
65
-
66
- # Prefer the original supplier listing if still active
60
+ # Prefer the original supplier listing if still active the most
61
+ # authoritative reference when the item actually has one (a plain
62
+ # Listing-based order line).
67
63
  if item.listing && Listing.active.exists?(id: item.listing.id)
68
64
  return item.listing
69
65
  end
70
66
 
67
+ # A feed-purchased line (no listing_id at all) should validate against
68
+ # the aggregator's own markup price, not a random supplier listing —
69
+ # even after a :supplier-kind item's source has been resolved straight
70
+ # to the fulfilling Business (see AggregatorListing#confirming_business),
71
+ # so this can't just check item.source's class anymore. Does the
72
+ # order's own business (order.ordered_to) have an active aggregator
73
+ # listing for this product at all? If so, that's the reference the
74
+ # retailer actually agreed to pay, regardless of what source is now.
75
+ if item.listing_id.nil? && item.order&.ordered_to.present?
76
+ aggregator_listing = AggregatorListing.active
77
+ .where(aggregator_id: item.order.ordered_to_id)
78
+ .detect { |al| al.product&.id == item.product_id }
79
+ return aggregator_listing if aggregator_listing
80
+ end
81
+
71
82
  # Fallback: find cheapest active listing for the product
72
83
  Listing.active
73
84
  .joins(:supplier_product)
@@ -1,5 +1,5 @@
1
1
  module Dscf
2
2
  module Marketplace
3
- VERSION = "0.13.3".freeze
3
+ VERSION = "0.13.5".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.3
4
+ version: 0.13.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Asrat