Class: ListingIssue

Inherits:
ApplicationRecord show all
Defined in:
app/models/listing_issue.rb

Overview

A single problem with a catalog item's marketplace listing as reported by the
marketplace's own API — Amazon suppression / SP-API listing issues, Walmart
unpublished reasons, Wayfair catalog-sync gaps, and (as integrations land)
other providers.

Distinct from CatalogItemRetailerProbe, which scrapes the public retailer
page: a listing issue is what the channel says is wrong with our listing,
not what our scraper could or couldn't read. Rows are reconciled by
ListingIssues::Sync (per provider): current issues are upserted on the
(catalog_item_id, provider, fingerprint) key and issues no longer reported
are auto-resolved, so the open set always mirrors the channel. Surfaced as the
"Listing Issues" column in the daily retailer-compliance report and reviewed /
resolved in the CRM Listing Issues dashboard.

Constant Summary collapse

PROVIDERS =

Recognised providers (the marketplace the issue came from). retailer_probe
is not a marketplace itself — it's our own probe's cross-retailer signal
(see ListingIssues::RetailerProbeAdapter); website is our own storefront
(see ListingIssues::WebsiteAdapter), where WE are the channel.

%w[amazon walmart wayfair google retailer_probe website].freeze
SEVERITIES =

Severity buckets, worst first. critical is a listing we independently
verified is broken — the retailer page loads (ruling out a scraper/network
blip) but the product is not buyable or not on it; self-verified live
revenue loss, so it outranks everything. error blocks/suppresses the
listing per the channel's own signal (suppression, NOT_LIVE, missing
required spec). warning is advisory (the listing still shows).

%w[critical error warning].freeze

Constants included from Models::Schedulable

Models::Schedulable::SIMPLE_FORM_OPTIONS

Instance Attribute Summary collapse

Belongs to collapse

Has one collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from ApplicationRecord

ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation

Methods included from Models::Schedulable

config

Methods included from Models::AfterCommittable

#after_commit

Methods included from Models::EventPublishable

#publish_event

Instance Attribute Details

#codeString

Returns:

  • (String)


79
# File 'app/models/listing_issue.rb', line 79

validates :code, presence: true

#fingerprintString

Returns:

  • (String)


85
# File 'app/models/listing_issue.rb', line 85

validates :fingerprint, presence: true, uniqueness: { scope: %i[catalog_item_id provider] }

#providerString

Returns:

  • (String)


76
# File 'app/models/listing_issue.rb', line 76

validates :provider, presence: true, inclusion: { in: PROVIDERS }

#severityString

Returns:

  • (String)


82
# File 'app/models/listing_issue.rb', line 82

validates :severity, inclusion: { in: SEVERITIES }

Class Method Details

.by_skuActiveRecord::Relation<ListingIssue>

A relation of ListingIssues that are by sku. Active Record Scope

Returns:

See Also:



97
98
99
100
101
# File 'app/models/listing_issue.rb', line 97

scope :by_sku, ->(sku) {
  return all if sku.blank?

  joins(catalog_item: { store_item: :item }).where('items.sku ILIKE ?', "%#{sku.strip}%")
}

.criticalActiveRecord::Relation<ListingIssue>

A relation of ListingIssues that are critical. Active Record Scope

Returns:

See Also:



90
# File 'app/models/listing_issue.rb', line 90

scope :critical,     -> { where(severity: 'critical') }

.errorsActiveRecord::Relation<ListingIssue>

A relation of ListingIssues that are errors. Active Record Scope

Returns:

See Also:



91
# File 'app/models/listing_issue.rb', line 91

scope :errors,       -> { where(severity: 'error') }

.for_providerActiveRecord::Relation<ListingIssue>

A relation of ListingIssues that are for provider. Active Record Scope

Returns:

See Also:



89
# File 'app/models/listing_issue.rb', line 89

scope :for_provider, ->(provider) { where(provider:) }

.openActiveRecord::Relation<ListingIssue>

A relation of ListingIssues that are open. Active Record Scope

Returns:

See Also:



87
# File 'app/models/listing_issue.rb', line 87

scope :open,         -> { where(resolved_at: nil) }

.open_item_idsActiveRecord::Relation<ListingIssue>

A relation of ListingIssues that are open item ids. Active Record Scope

Returns:

See Also:



95
# File 'app/models/listing_issue.rb', line 95

scope :open_item_ids, -> { open.distinct.select(:catalog_item_id) }

.resolvedActiveRecord::Relation<ListingIssue>

A relation of ListingIssues that are resolved. Active Record Scope

Returns:

See Also:



88
# File 'app/models/listing_issue.rb', line 88

scope :resolved,     -> { where.not(resolved_at: nil) }

.warningsActiveRecord::Relation<ListingIssue>

A relation of ListingIssues that are warnings. Active Record Scope

Returns:

See Also:



92
# File 'app/models/listing_issue.rb', line 92

scope :warnings,     -> { where(severity: 'warning') }

Instance Method Details

#catalogCatalog

Returns:



72
# File 'app/models/listing_issue.rb', line 72

has_one :catalog, through: :catalog_item

#catalog_itemCatalogItem

Returns:



67
# File 'app/models/listing_issue.rb', line 67

belongs_to :catalog_item

#open?Boolean

Returns true while the issue is still open (unresolved).

Returns:

  • (Boolean)

    true while the issue is still open (unresolved).



104
105
106
# File 'app/models/listing_issue.rb', line 104

def open?
  resolved_at.nil?
end

#resolve!(note: nil) ⇒ Boolean

Mark the issue resolved. Resolution is detection-derived only — called by
ListingIssues::Sync when the provider stops reporting the issue. There is
no manual "mark fixed": an auto-detected issue is open while detected and
resolved when it's gone.

Parameters:

  • note (String, nil) (defaults to: nil)

    resolution note (why it resolved)

Returns:

  • (Boolean)


119
120
121
# File 'app/models/listing_issue.rb', line 119

def resolve!(note: nil)
  update!(resolved_at: Time.current, resolution_note: note)
end

#resolved?Boolean

Returns true once the issue has been resolved.

Returns:

  • (Boolean)

    true once the issue has been resolved.



109
110
111
# File 'app/models/listing_issue.rb', line 109

def resolved?
  resolved_at.present?
end

#resolved_byEmployeeRecord?

Returns:



69
# File 'app/models/listing_issue.rb', line 69

belongs_to :resolved_by, class_name: 'EmployeeRecord', optional: true