Class: Maintenance::ItemMaintenance

Inherits:
BaseService show all
Defined in:
app/services/maintenance/item_maintenance.rb

Instance Method Summary collapse

Methods inherited from BaseService

#initialize, #log_debug, #log_error, #log_info, #log_warning, #logger, #options, #tagged_logger

Constructor Details

This class inherits a constructor from BaseService

Instance Method Details

#consolidate_specsObject

Goal is to consolidate duplicate specs that exist on multiple items into one spec



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'app/services/maintenance/item_maintenance.rb', line 38

def consolidate_specs
  sql = <<-SQL
    SELECT token, grouping, sku_regexp, method, text_blurb, units, image_id, formatter, array_agg(id) AS spec_ids
    FROM product_specifications
    WHERE product_line_id IS NULL and product_category_id IS NULL
    GROUP BY token, grouping, sku_regexp, method, text_blurb, units, image_id, formatter
    HAVING COUNT(id) > 1
    ORDER BY token, grouping, sku_regexp, method, text_blurb, units, image_id, formatter
    ---LIMIT 3
  SQL

  # Execute the SQL and get the result
  result = ActiveRecord::Base.connection.select_all(sql)

  # Extract the spec_ids from the result
  grouped_spec_ids = result.map { |row| PG::TextDecoder::Array.new.decode(row['spec_ids']) }
  logger.info "Found #{grouped_spec_ids.flatten.count} duplicate specs"
  survivor_spec_ids = {}

  grouped_spec_ids.each do |spec_ids|
    spec_ids = spec_ids.map(&:to_i)
    ProductSpecification.transaction do
      # What are the item ids
      collected_item_ids = Item.joins(:direct_product_specifications).where(product_specifications: { do_not_merge: false, id: spec_ids }).distinct.pluck(:id)
      # Keep the first one and destroy the rest
      primary_spec_id = spec_ids.shift
      # Delete the redundant specs
      ProductSpecification.where(id: spec_ids).destroy_all
      # Save the collected item ids into the primary spec
      survivor_spec = ProductSpecification.find(primary_spec_id)
      new_item_ids = collected_item_ids
      survivor_spec_ids[primary_spec_id] = { spec_ids_merged: spec_ids, item_ids: new_item_ids }
      survivor_spec.item_ids = new_item_ids
      survivor_spec.should_auto_translate = true
      survivor_spec.save!
    end
  end
  survivor_spec_ids
end

#discontinue_old_custom_matsObject



92
93
94
95
96
97
98
# File 'app/services/maintenance/item_maintenance.rb', line 92

def discontinue_old_custom_mats
  logger.info 'Discontinuing old custom mats'
  old_mats = Item.active.joins(:product_category).where(
    "product_categories.url LIKE '%-custom-%' and items.created_at < ? and not exists(select 1 from store_items si where si.item_id = items.id and si.qty_on_hand > 0)", 1.year.ago
  )
  old_mats.find_each(&:discontinue_self_and_dependents)
end

#discontinue_old_unused_itemsObject



88
89
90
# File 'app/services/maintenance/item_maintenance.rb', line 88

def discontinue_old_unused_items
  Maintenance::Item::DiscontinueUnusedItems.new.process
end

#discontinue_pending_discontinued_catalog_items(older_than = nil) ⇒ Object

Transitions pending_discontinue catalog items to discontinued once their
wait period has elapsed. Orchestrators can define a custom lifetime via
pending_discontinue_lifetime_duration (e.g. Menard: 1 week). Items in
catalogs without a custom lifetime use the default of 1 day.

Parameters:

  • older_than (Time, nil) (defaults to: nil)

    manual override that bypasses orchestrator
    lookup and applies to ALL items (for console/testing use)



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'app/services/maintenance/item_maintenance.rb', line 130

def discontinue_pending_discontinued_catalog_items(older_than = nil)
  all_pending = CatalogItem.where(state: 'pending_discontinue')
  total = 0

  if older_than
    total += transition_batch(all_pending, older_than)
  else
    custom_lifetimes = Edi::BaseOrchestrator.catalog_id_to_pending_discontinue_lifetime
    default_cutoff = Edi::BaseOrchestrator::DEFAULT_PENDING_DISCONTINUE_LIFETIME.ago

    if custom_lifetimes.any?
      custom_catalog_ids = custom_lifetimes.keys

      custom_lifetimes.each do |catalog_id, lifetime|
        batch = all_pending.where(catalog_id: catalog_id)
        total += transition_batch(batch, lifetime.ago)
      end

      remainder = all_pending.where.not(catalog_id: custom_catalog_ids)
      total += transition_batch(remainder, default_cutoff)
    else
      total += transition_batch(all_pending, default_cutoff)
    end
  end

  logger.debug("Discontinued catalog items pending_discontinue", catalog_item_count: total)
  total
end

#processObject



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'app/services/maintenance/item_maintenance.rb', line 2

def process
  jobs = %i[discontinue_old_custom_mats
            discontinue_old_unused_items
            propagate_discontinued_items
            propagate_discontinued_store_items
            discontinue_pending_discontinued_catalog_items
            synchronize_asins
            remove_empty_specs
            consolidate_specs
            spec_refresh
            synchronize_secondary_product_lines]
  results = {}
  jobs.each do |job_name|
    job_identity = "Maintenance::ItemMaintenance - #{job_name}"
    PaperTrail.request(whodunnit: job_identity) do
      logger.tagged(job_identity) do
        logger.info 'Starting Job'
        results[job_name] = send(job_name)
        logger.info 'Job finished'
      end
    end
  end
  results
end

#propagate_discontinued_itemsObject



100
101
102
103
104
105
106
107
108
109
# File 'app/services/maintenance/item_maintenance.rb', line 100

def propagate_discontinued_items
  # update store_items set is_discontinued = true
  # where exists(select 1 from items where items.id = store_items.item_id and items.is_discontinued);
  store_items = StoreItem.where(is_discontinued: false)
                         .where('exists(select 1 from items where items.id = store_items.item_id and items.is_discontinued)')
  store_item_ids = store_items.pluck(:id)
  res = store_items.update_all(is_discontinued: true)
  logger.debug("Propagated item discontinued", store_item_count: res)
  res
end

#propagate_discontinued_store_itemsObject



111
112
113
114
115
116
117
118
119
120
121
# File 'app/services/maintenance/item_maintenance.rb', line 111

def propagate_discontinued_store_items
  # Exclude discontinued: store-item propagation must not resurrect a catalog row that
  # already completed the discontinue lifecycle (typo was "discontued", so discontinued
  # rows were incorrectly forced back to pending_discontinue nightly).
  catalog_items = CatalogItem.where.not(state: %w[pending_discontinue discontinued])
                             .where('exists(select 1 from store_items si where si.id = catalog_items.store_item_id and si.is_discontinued = true )')
  catalog_item_ids = catalog_items.pluck(:id)
  res = catalog_items.update_all(state: 'pending_discontinue')
  logger.debug("Propagated store item discontinued", catalog_item_count: res)
  res
end

#remove_empty_specsObject



31
32
33
34
35
# File 'app/services/maintenance/item_maintenance.rb', line 31

def remove_empty_specs
  empty_specs = ProductSpecification.where(text_blurb: [nil, '']).where(method: 'text').where(template: false)
  logger.debug("Removing empty specs", count: empty_specs.count)
  empty_specs.delete_all
end

#spec_refreshObject



27
28
29
# File 'app/services/maintenance/item_maintenance.rb', line 27

def spec_refresh
  Item.async_update_all_items_product_specifications
end

#synchronize_asinsObject



78
79
80
81
82
83
84
85
86
# File 'app/services/maintenance/item_maintenance.rb', line 78

def synchronize_asins
  logger.info 'Synchronizing Asins from Item to Catalog Items'
  CatalogItem.transaction do
    CatalogItem.amazons_with_asins.eager_load(:item).where('catalog_items.third_party_part_number IS NULL OR items.amazon_asin <> catalog_items.third_party_part_number').find_each do |ci|
      logger.info "Synchronizing catalog item #{ci.id}, old tppn: #{ci.third_party_part_number}, new: #{ci.item.amazon_asin}"
      ci.update_column(:third_party_part_number, ci.item.amazon_asin)
    end
  end
end

#synchronize_secondary_product_linesObject

Ensures the primary product line is also in the item_product_lines table



160
161
162
163
164
165
166
167
168
169
170
171
# File 'app/services/maintenance/item_maintenance.rb', line 160

def synchronize_secondary_product_lines
  sql = <<-SQL
    INSERT INTO item_product_lines (item_id, product_line_id, position)
      select i.id, i.primary_product_line_id, (select count(*) from item_product_lines ipl where ipl.item_id = i.id) + 1
      from items i
      where
      i.primary_product_line_id IS NOT NULL
      AND NOT EXISTS(select 1 from item_product_lines ipl where ipl.item_id = i.id and ipl.product_line_id = i.primary_product_line_id)
  SQL

  ActiveRecord::Base.connection.execute(sql)
end