Class: ListingIssues::AmazonAdapter

Inherits:
BaseAdapter show all
Defined in:
app/services/listing_issues/amazon_adapter.rb

Overview

Amazon listing issues, sourced from signals the SP-API pulls already persist:
active has_issues AmazonCatalogItemFlag rows (the SP-API listing issues
array, joined into text by Catalog::AmazonPricingAutomationService) and
CatalogItem#item_suppressed_status. Reads stored data only — never calls
Amazon — so it can't block on rate limits and stays consistent with whatever
the pricing / listings pulls last recorded.

Constant Summary collapse

PROVIDER =

Provider key stamped on every Issue this adapter emits.

'amazon'
SEGMENT_BOUNDARY =

The joined issues text is split back into per-issue segments on the
boundary before a "[xx_XX] " locale marker, so a ';' inside a message
doesn't fracture it.

/;\s+(?=\[[a-z]{2}_[A-Z]{2}\]\s)/
SEGMENT_FORMAT =

"[en_US] ERROR: 13013 - message" → locale / severity word / code / message.

/\A\[(?<locale>[a-z]{2}_[A-Z]{2})\]\s+(?<sev>ERROR|WARNING|INFO)?:?\s*(?<code>\d+)?\s*-?\s*(?<msg>.*)\z/m
PRODUCT_TYPE_DESIRED_MISMATCH_SQL =

Static SQL (column references only — no interpolation) mirroring
#issues_from_desired_product_type so the bulk sweep only visits genuine
mismatches. Two independent axes: the LISTING should carry the submittable
type (amazon_product_schema, else desired) and the CATALOG should carry the
desired type. The intended catalog≠listing split (desired=TOWEL_WARMER,
schema/listing=TOWEL_HOLDER) is therefore NOT selected.

<<~SQL.squish.freeze
  COALESCE(NULLIF(catalog_items.amazon_desired_product_type, ''), NULLIF(variant_groups.channel_settings->>'amazon_desired_product_type', '')) IS NOT NULL
  AND (
    (NULLIF(catalog_items.amazon_listing_reported_product_type, '') IS NOT NULL
      AND NULLIF(catalog_items.amazon_listing_reported_product_type, '')
          <> COALESCE(NULLIF(catalog_items.amazon_product_schema, ''), NULLIF(variant_groups.channel_settings->>'amazon_product_schema', ''),
                      NULLIF(catalog_items.amazon_desired_product_type, ''), NULLIF(variant_groups.channel_settings->>'amazon_desired_product_type', '')))
    OR (NULLIF(catalog_items.amazon_reported_product_type, '') IS NOT NULL
      AND NULLIF(catalog_items.amazon_reported_product_type, '')
          <> COALESCE(NULLIF(catalog_items.amazon_desired_product_type, ''), NULLIF(variant_groups.channel_settings->>'amazon_desired_product_type', '')))
  )
SQL
PRODUCT_CATEGORY_DESIRED_MISMATCH_SQL =

Static SQL mirroring #issues_from_desired_category so the bulk sweep only
visits genuine category mismatches: the catalog has a marketplace, the item
has a desired browse node for that marketplace, and Amazon's reported browse
classification (stored catalog payload, any locale) is NOT one of those
desired nodes. The precise compare stays here rather than scanning every
item that merely has a browse node.

<<~SQL.squish.freeze
  catalogs.amazon_marketplace_id IS NOT NULL
  AND EXISTS (
    SELECT 1 FROM amazon_browse_nodes_catalog_items dci
    JOIN amazon_browse_nodes dn ON dn.id = dci.amazon_browse_node_id
    WHERE dci.catalog_item_id = catalog_items.id
      AND dn.amazon_marketplace_id = catalogs.amazon_marketplace_id
  )
  AND EXISTS (
    SELECT 1
    FROM jsonb_each(catalog_items.retailer_information) AS loc(key, value)
    CROSS JOIN LATERAL jsonb_array_elements(
      CASE
        WHEN jsonb_typeof(value #> '{catalog,payload,summaries}') = 'array'
          THEN value #> '{catalog,payload,summaries}'
        ELSE '[]'::jsonb
      END
    ) AS summary(value)
    WHERE (summary.value #>> '{browseClassification,classificationId}') IS NOT NULL
      AND NOT EXISTS (
        SELECT 1 FROM amazon_browse_nodes_catalog_items mci
        JOIN amazon_browse_nodes mn ON mn.id = mci.amazon_browse_node_id
        WHERE mci.catalog_item_id = catalog_items.id
          AND mn.amazon_marketplace_id = catalogs.amazon_marketplace_id
          AND mn.node_id = (summary.value #>> '{browseClassification,classificationId}')
      )
  )
SQL

Instance Method Summary collapse

Instance Method Details

#candidate_item_idsArray<Integer>

Returns:

  • (Array<Integer>)


79
80
81
82
83
# File 'app/services/listing_issues/amazon_adapter.rb', line 79

def candidate_item_ids
  (has_issues_item_ids + suppressed_item_ids + open_issue_item_ids +
   product_type_desired_mismatch_item_ids + desired_category_mismatch_item_ids +
   buy_box_lost_item_ids + variation_product_type_divergent_item_ids).uniq
end

#eager_loadArray

Everything the checks above touch, eager-loaded by the bulk sweep.

Returns:

  • (Array)


104
105
106
# File 'app/services/listing_issues/amazon_adapter.rb', line 104

def eager_load
  %i[amazon_catalog_item_flags catalog amazon_browse_nodes variant_group]
end

#issues_for(catalog_item) ⇒ Array<ListingIssues::Issue>

Parameters:

Returns:



87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'app/services/listing_issues/amazon_adapter.rb', line 87

def issues_for(catalog_item)
  # Only active listings are actionable. Returning [] for non-active items
  # (which can still be candidates via open_issue_item_ids) lets the sync
  # auto-resolve issues left over from an item that has since been
  # discontinued — keeping the dashboard to the live catalog.
  return [] unless catalog_item.state == 'active'

  issues_from_flags(catalog_item) +
    issues_from_suppression(catalog_item) +
    issues_from_desired_product_type(catalog_item) +
    issues_from_desired_category(catalog_item) +
    issues_from_buy_box_loss(catalog_item) +
    issues_from_variation_product_type_divergence(catalog_item)
end

#providerString

Returns:

  • (String)


76
# File 'app/services/listing_issues/amazon_adapter.rb', line 76

def provider = PROVIDER