Class: Store

Inherits:
ApplicationRecord show all
Includes:
Models::Auditable
Defined in:
app/models/store.rb

Overview

== Schema Information

Table name: stores
Database name: primary

id :integer not null, primary key
base_handling_charge :decimal(8, 2)
contact_email :string(255)
contact_number :string(255)
country_iso3 :string(3)
fax_number :string(255)
jde_branch :string(255)
name :string(255)
order_notification_emails :string default([]), is an Array
owner :string
short_name :string(255)
tax_identification_number_for_shipping :string(255)
warehouse_email :string
company_id :integer
consignee_party_id :integer
customer_for_st_id :integer
mailing_address_id :integer
preferred_inbound_shipping_option_id :integer
preferred_outbound_shipping_option_id :integer
primary_catalog_id :integer
supplier_id :integer
warehouse_address_id :integer
warehouse_service_address_id :integer

Indexes

idx_country_iso3 (country_iso3)
idx_owner (owner)
index_stores_on_company_id (company_id)
index_stores_on_supplier_id (supplier_id)

Foreign Keys

fk_rails_... (consignee_party_id => parties.id)
fk_rails_... (customer_for_st_id => parties.id)
fk_rails_... (supplier_id => parties.id)

Constant Summary collapse

WARMLYYOURS_US_ID =

= Store.find(1)

1
WARMLYYOURS_CA_ID =

= Store.find(2)

2
PUBLIC_STORE_IDS =

Public store ids.

[WARMLYYOURS_US_ID, WARMLYYOURS_CA_ID].freeze
STORE_TO_LOCALE =

Store to locale.

{ 'WarmlyYours-US' => :'en-US', 'WarmlyYours-CA' => :'en-CA' }.freeze
LOCALE_TO_STORE =

Locale to store.

{
  en: 'WarmlyYours-US',
  'en-US': 'WarmlyYours-US',
  'en-CA': 'WarmlyYours-CA',
  'fr-CA': 'WarmlyYours-CA'
}.freeze
LOCALE_TO_STORE_ID =

Locale to store id.

{
  en: 1,
  'en-US': 1,
  'en-CA': 2,
  'fr-CA': 2
}.freeze
CURRENCY_TO_STORE =

Currency to store.

{ 'USD' => 'WarmlyYours-US', 'CAD' => 'WarmlyYours-CA' }.freeze
CARRIERS_FOR_MASS_SHIP_CONFIRM =

Carriers for mass ship confirm.

{ 'WarmlyYours-US' => %w[FedExGround FedExExpress FedExFreight UPS USPS],
'WarmlyYours-CA' => %w[FedExGround FedExExpress FedExFreight UPS Canadapost Canpar Purolator] }.freeze
CARRIER_PICKUP_TIMES =

Carrier pickup times.

{
  'WarmlyYours-US' => { 'FedExGround' => '16:30:00', 'FedExExpress' => '16:00:00', 'FedExFreight' => '17:00:00', 'UPS' => '17:30:00',
                        'USPS' => '14:00:00' }, 'WarmlyYours-CA' => { 'FedExGround' => '14:00:00', 'FedExFreight' => '17:00:00', 'UPS' => '16:00:00', 'Purolator' => '15:30:00', 'Canadapost' => '15:30:00', 'Canpar' => '14:30:00' }
}.freeze
CARRIER_PICKUP_CUT_OFF_TIMES =

Carrier pickup cut off times.

{ 'WarmlyYours-US' => { 'FedExFreight' => '12:00:00', 'ShipengineSaia' => '14:00:00', 'USPS' => '14:55:00' },
                                   'WarmlyYours-CA' => { 'FedEx' => '14:00:00', 'FedExFreight' => '12:00:00', 'Canadapost' => '14:55:00',
'Canpar' => '10:55:00' } }.freeze

Constants included from Models::Auditable

Models::Auditable::ALWAYS_IGNORED

Constants included from Models::Schedulable

Models::Schedulable::SIMPLE_FORM_OPTIONS

Belongs to collapse

Has one collapse

Has many collapse

Has and belongs to many collapse

Instance Attribute Summary collapse

Delegated Instance Attributes collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Models::Auditable

#all_skipped_columns, #audit_reference_data, #should_not_save_version, #stamp_record

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

#carrier_pickupsActiveRecord::Associations::CollectionProxy<CarrierPickup>

Returns scheduled carrier pickups.

Returns:

  • (ActiveRecord::Associations::CollectionProxy<CarrierPickup>)

    scheduled carrier pickups



148
# File 'app/models/store.rb', line 148

has_many :carrier_pickups, dependent: :destroy

#catalogsActiveRecord::Associations::CollectionProxy<Catalog>

Returns catalogs selling from this store.

Returns:

  • (ActiveRecord::Associations::CollectionProxy<Catalog>)

    catalogs selling from this store



130
# File 'app/models/store.rb', line 130

has_many :catalogs, inverse_of: :store

#companyCompany?

Returns the company operating this store.

Returns:

  • (Company, nil)

    the company operating this store



98
# File 'app/models/store.rb', line 98

belongs_to :company, optional: true

#company_idInteger

Returns required company reference.

Returns:

  • (Integer)

    required company reference



183
# File 'app/models/store.rb', line 183

validates :company_id, :name, :country_iso3, :warehouse_address, presence: true

#consignee_partyCustomer?

Returns the consignment customer for this store.

Returns:

  • (Customer, nil)

    the consignment customer for this store



101
# File 'app/models/store.rb', line 101

belongs_to :consignee_party, class_name: 'Customer', inverse_of: :consignment_stores, optional: true

#contact_emailString

Returns contact email address.

Returns:

  • (String)

    contact email address



192
# File 'app/models/store.rb', line 192

validates :contact_email, email_format: true

#contact_numberString

Returns contact phone number.

Returns:

  • (String)

    contact phone number



198
# File 'app/models/store.rb', line 198

validates :contact_number, phone_format: true

#countryCountry?

Returns the store's country (keyed by iso3).

Returns:

  • (Country, nil)

    the store's country (keyed by iso3)



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

belongs_to :country, foreign_key: :country_iso3, primary_key: :iso3, optional: true

#country_iso3String

Returns required ISO3 country code.

Returns:

  • (String)

    required ISO3 country code



183
# File 'app/models/store.rb', line 183

validates :company_id, :name, :country_iso3, :warehouse_address, presence: true

#customer_for_stCustomer?

Returns the customer used for store transfers.

Returns:

  • (Customer, nil)

    the customer used for store transfers



104
# File 'app/models/store.rb', line 104

belongs_to :customer_for_st, class_name: 'Customer', optional: true

#cycle_countsActiveRecord::Associations::CollectionProxy<CycleCount>

Returns inventory cycle counts for this store.

Returns:

  • (ActiveRecord::Associations::CollectionProxy<CycleCount>)

    inventory cycle counts for this store



142
# File 'app/models/store.rb', line 142

has_many :cycle_counts

#floor_typesActiveRecord::Associations::CollectionProxy<FloorType>

Returns floor types this store sells into.

Returns:

  • (ActiveRecord::Associations::CollectionProxy<FloorType>)

    floor types this store sells into



170
# File 'app/models/store.rb', line 170

has_and_belongs_to_many :floor_types

#heating_element_product_line_optionsActiveRecord::Associations::CollectionProxy<HeatingElementProductLineOption>

Returns heating element options for this store.

Returns:



151
# File 'app/models/store.rb', line 151

has_many :heating_element_product_line_options, inverse_of: :store

#invoicesActiveRecord::Associations::CollectionProxy<Invoice>

Returns invoices issued by this store.

Returns:

  • (ActiveRecord::Associations::CollectionProxy<Invoice>)

    invoices issued by this store



166
# File 'app/models/store.rb', line 166

has_many :invoices

#itemsActiveRecord::Associations::CollectionProxy<Item>

Returns items stocked, via store_items.

Returns:

  • (ActiveRecord::Associations::CollectionProxy<Item>)

    items stocked, via store_items



127
# File 'app/models/store.rb', line 127

has_many :items, through: :store_items

#mailing_addressAddress?

Returns the store's mailing address.

Returns:

  • (Address, nil)

    the store's mailing address



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

belongs_to :mailing_address, class_name: 'Address', optional: true

#manifestsActiveRecord::Associations::CollectionProxy<Manifest>

Returns shipping manifests for this store.

Returns:

  • (ActiveRecord::Associations::CollectionProxy<Manifest>)

    shipping manifests for this store



154
# File 'app/models/store.rb', line 154

has_many :manifests, inverse_of: :store

#nameString

Returns required store name.

Returns:

  • (String)

    required store name

Validations:



183
# File 'app/models/store.rb', line 183

validates :company_id, :name, :country_iso3, :warehouse_address, presence: true

#order_notification_emailsArray<String>

Returns emails notified on new orders.

Returns:

  • (Array<String>)

    emails notified on new orders



189
# File 'app/models/store.rb', line 189

validates :order_notification_emails, email_format: true

#preferred_inbound_shipping_optionShippingOption?

Returns the default inbound shipping option.

Returns:



113
# File 'app/models/store.rb', line 113

belongs_to :preferred_inbound_shipping_option, class_name: 'ShippingOption', optional: true

#preferred_outbound_shipping_optionShippingOption?

Returns the default outbound shipping option.

Returns:



116
# File 'app/models/store.rb', line 116

belongs_to :preferred_outbound_shipping_option, class_name: 'ShippingOption', optional: true

#primary_catalogCatalog?

Returns the store's primary catalog.

Returns:

  • (Catalog, nil)

    the store's primary catalog



110
# File 'app/models/store.rb', line 110

belongs_to :primary_catalog, class_name: 'Catalog', optional: true

#printersActiveRecord::Associations::CollectionProxy<Printer>

Returns printers installed at this store.

Returns:

  • (ActiveRecord::Associations::CollectionProxy<Printer>)

    printers installed at this store



160
# File 'app/models/store.rb', line 160

has_many :printers

#purchase_ordersActiveRecord::Associations::CollectionProxy<PurchaseOrder>

Returns purchase orders for this store.

Returns:

  • (ActiveRecord::Associations::CollectionProxy<PurchaseOrder>)

    purchase orders for this store



157
# File 'app/models/store.rb', line 157

has_many :purchase_orders

#room_typesActiveRecord::Associations::CollectionProxy<RoomType>

Returns room types this store sells into.

Returns:

  • (ActiveRecord::Associations::CollectionProxy<RoomType>)

    room types this store sells into



173
# File 'app/models/store.rb', line 173

has_and_belongs_to_many :room_types

#sales_rep_queueSalesRepQueue?

Returns the store's sales rep assignment queue.

Returns:

  • (SalesRepQueue, nil)

    the store's sales rep assignment queue



120
# File 'app/models/store.rb', line 120

has_one :sales_rep_queue

#serial_numbersActiveRecord::Associations::CollectionProxy<SerialNumber>

Returns serial numbers via store_items.

Returns:

  • (ActiveRecord::Associations::CollectionProxy<SerialNumber>)

    serial numbers via store_items



145
# File 'app/models/store.rb', line 145

has_many :serial_numbers, through: :store_items

#short_nameString

Returns unique short name.

Returns:

  • (String)

    unique short name



186
# File 'app/models/store.rb', line 186

validates :name, :short_name, uniqueness: true

#storage_locationsActiveRecord::Associations::CollectionProxy<StorageLocation>

Returns storage locations within this store.

Returns:

  • (ActiveRecord::Associations::CollectionProxy<StorageLocation>)

    storage locations within this store



163
# File 'app/models/store.rb', line 163

has_many :storage_locations

#store_itemsActiveRecord::Associations::CollectionProxy<StoreItem>

Returns items stocked at this store.

Returns:

  • (ActiveRecord::Associations::CollectionProxy<StoreItem>)

    items stocked at this store



124
# File 'app/models/store.rb', line 124

has_many :store_items, dependent: :destroy

#store_transfersActiveRecord::Associations::CollectionProxy<StoreTransfer>

Returns transfers out of this store.

Returns:

  • (ActiveRecord::Associations::CollectionProxy<StoreTransfer>)

    transfers out of this store



133
# File 'app/models/store.rb', line 133

has_many :store_transfers, foreign_key: :from_store_id, primary_key: :id

#supplierSupplier

Returns the supplier backing this store.

Returns:

  • (Supplier)

    the supplier backing this store



107
# File 'app/models/store.rb', line 107

belongs_to :supplier, class_name: 'Supplier'

#uploadsActiveRecord::Associations::CollectionProxy<Upload>

Returns files attached to this store.

Returns:

  • (ActiveRecord::Associations::CollectionProxy<Upload>)

    files attached to this store



139
# File 'app/models/store.rb', line 139

has_many :uploads, as: :resource, dependent: :destroy

#warehouse_addressAddress?

Returns the warehouse's physical address.

Returns:

  • (Address, nil)

    the warehouse's physical address

Validations:



86
# File 'app/models/store.rb', line 86

belongs_to :warehouse_address, class_name: 'Address', optional: true

#warehouse_emailString?

Returns warehouse email address.

Returns:

  • (String, nil)

    warehouse email address



195
# File 'app/models/store.rb', line 195

validates :warehouse_email, email_format: true, allow_blank: true

#warehouse_packagesActiveRecord::Associations::CollectionProxy<WarehousePackage>

Returns packaging available at this store.

Returns:

  • (ActiveRecord::Associations::CollectionProxy<WarehousePackage>)

    packaging available at this store



136
# File 'app/models/store.rb', line 136

has_many :warehouse_packages, -> { order(:sort_order, :volume) }, dependent: :destroy

#warehouse_service_addressAddress?

Returns the warehouse's service address, when different.

Returns:

  • (Address, nil)

    the warehouse's service address, when different



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

belongs_to :warehouse_service_address, class_name: 'Address', optional: true

Class Method Details

.by_currency(currency) ⇒ Store?

Todo make this more I18n

Parameters:

  • currency (String)

    ISO currency code, e.g. "USD"

Returns:

  • (Store, nil)

    the store trading in that currency



279
280
281
# File 'app/models/store.rb', line 279

def self.by_currency(currency)
  find_by(name: CURRENCY_TO_STORE[currency])
end

.defaultStore?

Returns the default store (WarmlyYours-US).

Returns:

  • (Store, nil)

    the default store (WarmlyYours-US)



291
292
293
# File 'app/models/store.rb', line 291

def self.default
  find_by(name: 'WarmlyYours-US')
end

.locale_to_catalog(locale = nil) ⇒ Store

Todo make this more I18n

Parameters:

  • locale (Symbol, String, nil) (defaults to: nil)

    the locale (defaults to I18n.locale)

Returns:

  • (Store)

    the store serving that locale



271
272
273
274
# File 'app/models/store.rb', line 271

def self.locale_to_catalog(locale = nil)
  locale ||= I18n.locale
  Store.find(store_id_for_locale(locale))
end

.options_for_select(filters = nil) ⇒ Array<Array(String, Integer)>

Returns sorted name/id pairs.

Parameters:

  • filters (Hash, nil) (defaults to: nil)

    optional where conditions

Returns:

  • (Array<Array(String, Integer)>)

    sorted name/id pairs



297
298
299
# File 'app/models/store.rb', line 297

def self.options_for_select(filters = nil)
  where(filters).order(:name).pluck(:name, :id)
end

.potential_ownersArray<String>

Returns recognized owner slugs.

Returns:

  • (Array<String>)

    recognized owner slugs



213
214
215
# File 'app/models/store.rb', line 213

def self.potential_owners
  %w[warmlyyours amazon dimass]
end

.store_id_for_locale(locale = nil) ⇒ Integer?

Todo make this more I18n

Parameters:

  • locale (Symbol, String, nil) (defaults to: nil)

    the locale to map

Returns:

  • (Integer, nil)

    the store id serving that locale



264
265
266
# File 'app/models/store.rb', line 264

def self.store_id_for_locale(locale = nil)
  LOCALE_TO_STORE_ID[locale&.to_sym]
end

.store_locations_by_idHash{Integer => Array<String>}

Returns store id to its location labels.

Returns:

  • (Hash{Integer => Array<String>})

    store id to its location labels



367
368
369
# File 'app/models/store.rb', line 367

def self.store_locations_by_id
  Store.all.to_h { |s| [s.id, s.locations] }
end

.warmlyyours_warehousesActiveRecord::Relation<Store>

A relation of Stores that are warmlyyours warehouses. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Store>)

See Also:



208
# File 'app/models/store.rb', line 208

scope :warmlyyours_warehouses, -> { where(owner: 'warmlyyours') }

Instance Method Details

#alternative_storeStore

Todo make this more I18n

Returns:

  • (Store)

    the sister store (US ↔ CA)



241
242
243
# File 'app/models/store.rb', line 241

def alternative_store
  find(id == WARMLYYOURS_US_ID ? WARMLYYOURS_CA_ID : WARMLYYOURS_US_ID)
end

#alternative_storesActiveRecord::Relation<Store>

Returns other WarmlyYours-owned stores.

Returns:

  • (ActiveRecord::Relation<Store>)

    other WarmlyYours-owned stores



251
252
253
# File 'app/models/store.rb', line 251

def alternative_stores
  Store.where.not(id:).where(owner: 'warmlyyours')
end

#carrier_pickup_for_datetime(carrier_account_id, datetime) ⇒ CarrierPickup?

Returns the pickup at that datetime.

Parameters:

  • carrier_account_id (Integer)

    the carrier account to look up

  • datetime (Time)

    the scheduled time to match

Returns:



385
386
387
# File 'app/models/store.rb', line 385

def carrier_pickup_for_datetime(, datetime)
  carrier_pickups.().for_datetime(datetime).first
end

#carrier_pickup_for_today(carrier_account_id) ⇒ CarrierPickup?

Returns today's pickup for that carrier account.

Parameters:

  • carrier_account_id (Integer)

    the carrier account to look up

Returns:

  • (CarrierPickup, nil)

    today's pickup for that carrier account



378
379
380
# File 'app/models/store.rb', line 378

def carrier_pickup_for_today()
  carrier_pickups.().for_today.first
end

#carrier_pickup_time(carrier) ⇒ Time?

Returns today's scheduled pickup time for the carrier, when configured.

Parameters:

  • carrier (String)

    the carrier key, e.g. "FedExGround"

Returns:

  • (Time, nil)

    today's scheduled pickup time for the carrier, when configured



597
598
599
600
601
602
603
# File 'app/models/store.rb', line 597

def carrier_pickup_time(carrier)
  pu_time = nil
  if CARRIER_PICKUP_TIMES[name] && (pu_time_str = CARRIER_PICKUP_TIMES[name][carrier])
    pu_time = Time.zone.parse(pu_time_str)
  end
  pu_time
end

#country_codeString

Returns the two-letter country code.

Returns:

  • (String)

    the two-letter country code



302
303
304
# File 'app/models/store.rb', line 302

def country_code
  country.iso
end

#currencyObject

Alias for Company#currency

Returns:

  • (Object)

    Company#currency

See Also:



288
# File 'app/models/store.rb', line 288

delegate :currency, to: :company

#currency_symbolString

Returns the currency symbol for the company's currency.

Returns:

  • (String)

    the currency symbol for the company's currency



284
285
286
# File 'app/models/store.rb', line 284

def currency_symbol
  Money::Currency.new(currency).symbol
end

#current_shipping_document_print_profile(employee_record_id = nil) ⇒ PrintProfile?

Returns the shipping document print profile.

Parameters:

  • employee_record_id (Integer, nil) (defaults to: nil)

    optional employee to specialize for

Returns:

  • (PrintProfile, nil)

    the shipping document print profile



328
329
330
# File 'app/models/store.rb', line 328

def current_shipping_document_print_profile(employee_record_id = nil)
  printer_profile_for_scope(:shipping_document, employee_record_id)
end

#current_shipping_label_print_profile(employee_record_id = nil) ⇒ PrintProfile?

Returns the shipping label print profile.

Parameters:

  • employee_record_id (Integer, nil) (defaults to: nil)

    optional employee to specialize for

Returns:



334
335
336
# File 'app/models/store.rb', line 334

def current_shipping_label_print_profile(employee_record_id = nil)
  printer_profile_for_scope(:shipping_label, employee_record_id)
end

#current_upc_label_print_profile(employee_record_id = nil) ⇒ PrintProfile?

Returns the UPC label print profile.

Parameters:

  • employee_record_id (Integer, nil) (defaults to: nil)

    optional employee to specialize for

Returns:



322
323
324
# File 'app/models/store.rb', line 322

def current_upc_label_print_profile(employee_record_id = nil)
  printer_profile_for_scope(:upc_label, employee_record_id)
end

#default_shipping_methodString?

Returns the default shipping method key for the store's country.

Returns:

  • (String, nil)

    the default shipping method key for the store's country



357
358
359
# File 'app/models/store.rb', line 357

def default_shipping_method
  { USA: 'ground', CAN: 'standard' }.dig(country.iso3)
end

#emailString

Returns the warehouse notification email for this store's country.

Returns:

  • (String)

    the warehouse notification email for this store's country



348
349
350
351
352
353
354
# File 'app/models/store.rb', line 348

def email
  if country_iso3 == 'CAN'
    'warehousecanada@warmlyyours.com'
  else
    'warehouseusa@warmlyyours.com'
  end
end

#email_options_for_order_notification_emailsArray<String>

Returns unique, non-blank order notification emails.

Returns:

  • (Array<String>)

    unique, non-blank order notification emails



246
247
248
# File 'app/models/store.rb', line 246

def email_options_for_order_notification_emails
  [order_notification_emails].flatten.compact.uniq
end

#future_carrier_pickups(carrier_account_id) ⇒ ActiveRecord::Relation<CarrierPickup>

Returns future pickups for that carrier account.

Parameters:

  • carrier_account_id (Integer)

    the carrier account to look up

Returns:

  • (ActiveRecord::Relation<CarrierPickup>)

    future pickups for that carrier account



391
392
393
# File 'app/models/store.rb', line 391

def future_carrier_pickups()
  carrier_pickups.().future_days
end

#get_next_sales_rep_assignments_for_customer(customer) ⇒ Object?

Delegates to the store's sales rep queue for round-robin assignment.

Parameters:

  • customer (Customer)

    the customer being assigned

Returns:

  • (Object, nil)

    whatever the queue returns, nil without a queue



220
221
222
# File 'app/models/store.rb', line 220

def get_next_sales_rep_assignments_for_customer(customer)
  sales_rep_queue&.get_next_sales_rep_assignments_for_customer(customer)
end

#goods_accountString

Returns GL account for goods (purchased finished goods vs consignment).

Returns:

  • (String)

    GL account for goods (purchased finished goods vs consignment)



230
231
232
# File 'app/models/store.rb', line 230

def 
  warmlyyours_warehouse? ? PURCHASED_FINISHED_GOODS_ACCOUNT : CONSIGNMENT_ACCOUNT
end

#import_catalog(catalog) ⇒ void

This method returns an undefined value.

Import a catalog's store item into this store assuming that will be the only catalog for that store

Parameters:

  • catalog (Catalog)

    the source catalog to clone items from



398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
# File 'app/models/store.rb', line 398

def import_catalog(catalog)
  return if catalog.store_id == id
  return if catalog.store.country_iso3 != country_iso3

  # Now we can proceed, clone only active
  Catalog.transaction do
    # Clone catalog
    if (new_catalog = catalogs.first)
      logger.info "Existing catalog: #{new_catalog.name}"
    else
      new_catalog = catalog.dup
      new_catalog.store_id = id
      new_catalog.parent_catalog_id = nil
      new_catalog.price_sync_policy = :price_sync_manual
      new_catalog.price_sync_delay = nil
      new_catalog.save!
      logger.info "New catalog created: #{new_catalog.id}"
    end

    catalog.catalog_items.with_item.active.each do |ci|
      item_sku = ci.store_item.item.sku
      logger.info "Evaluating #{item_sku} from catalog #{catalog.name} for import into store id #{id} - #{name}"
      # Does this store item already exist?
      if (new_si = store_items.where(item_id: ci.store_item.item_id).first)
        logger.info 'Store item already exists'
      else
        logger.info 'New store item required'
        # clone the store item
        new_si = ci.store_item.dup
        new_si.qty_on_hand = 0
        new_si.qty_committed = 0
        new_si.unit_cogs = 0
        new_si.last_item_ledger_entry_id = nil
        new_si.store_id = id
        new_si.save!
        logger.info "New Store item saved as #{new_si.id}"
      end
      # Was this catalog item already mapped
      if (new_ci = new_si.catalog_items.first)
        logger.info 'Existing catalog item'
        new_ci.attributes = ci.attributes
      else
        logger.info 'New catalog item'
        new_ci = ci.dup
      end
      new_ci.catalog_id = new_catalog.id
      new_ci.store_item_id = new_si.id
      new_ci.save!
      logger.info "Catalog item #{new_ci.id} saved to catalog id #{new_catalog.id}"
    end
  end
end

#locales_servedArray<Symbol>

Todo make this more I18n, look at its catalog?

Returns:

  • (Array<Symbol>)

    locales served by this store's country



257
258
259
# File 'app/models/store.rb', line 257

def locales_served
  country.locales_for_country
end

#locationsArray<String>

Returns unique location labels across store items.

Returns:

  • (Array<String>)

    unique location labels across store items



362
363
364
# File 'app/models/store.rb', line 362

def locations
  store_items.pluck(:location).uniq
end

#mass_ship_confirm_counter(carrier) ⇒ Integer

Returns number of mass ship confirm candidates.

Parameters:

  • carrier (String)

    the carrier key, e.g. "FedExGround"

Returns:

  • (Integer)

    number of mass ship confirm candidates



591
592
593
# File 'app/models/store.rb', line 591

def mass_ship_confirm_counter(carrier)
  query_mass_ship_confirm_delivery_candidates(carrier).size
end

#printer_profile_for_scope(scope_name, employee_record_id = nil) ⇒ PrintProfile?

Resolves the print profile for a scope, preferring an employee-specific one.

Parameters:

  • scope_name (Symbol)

    the PrintProfile scope, e.g. :upc_label

  • employee_record_id (Integer, nil) (defaults to: nil)

    optional employee to specialize for

Returns:



310
311
312
313
314
315
316
317
318
# File 'app/models/store.rb', line 310

def printer_profile_for_scope(scope_name, employee_record_id = nil)
  profiles = PrintProfile.in_store_id(id).send(scope_name).order(:id)
  profiles = if employee_record_id && profiles.where(employee_record_id:).exists? # If there is a specificity use it
               profiles.where(employee_record_id:)
             else # Take the default
               profiles.where(employee_record_id: nil)
             end
  profiles.first
end

#query_awaiting_carrier_assignment_ordersActiveRecord::Relation<Order>

Returns orders awaiting carrier assignment.

Returns:

  • (ActiveRecord::Relation<Order>)

    orders awaiting carrier assignment



478
479
480
# File 'app/models/store.rb', line 478

def query_awaiting_carrier_assignment_orders
  Order.by_store_id(id).where(state: 'awaiting_carrier_assignment').with_associations
end

#query_mass_ship_confirm_delivery_candidates(carrier) ⇒ ActiveRecord::Relation<Delivery>

Deliveries eligible for mass ship confirm for a carrier, combining HW and
Amazon Buy Shipping rows and gating on the store's carrier pickup time.

Parameters:

  • carrier (String)

    the carrier key, e.g. "FedExGround"

Returns:

  • (ActiveRecord::Relation<Delivery>)

    candidate deliveries



553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
# File 'app/models/store.rb', line 553

def query_mass_ship_confirm_delivery_candidates(carrier)
  carrier_to_use = carrier
  carrier_to_use = 'FedEx' if %w[FedExGround FedExExpress].include?(carrier) # deal with annoying FedEx ground vs express

  hw_query = Delivery.by_store_id(id).with_shipment_carrier(carrier_to_use).pending_ship_confirm
  hw_query = hw_query.fedex_ground if carrier == 'FedExGround'
  hw_query = hw_query.fedex_express if carrier == 'FedExExpress'

  # Include AMZ Buy Shipping deliveries whose underlying physical carrier matches.
  # FedExGround captures all AMZ BS FedEx (Home Delivery, Ground, etc.) since the
  # amz_metadata doesn't reliably distinguish ground vs express service level.
  # FedExExpress is therefore HW-only to avoid double-counting.
  amz_query = if carrier == 'FedExExpress'
                Delivery.none
              else
                Delivery.by_store_id(id).with_amz_bs_carrier(carrier_to_use).pending_ship_confirm
              end

  # Gate by carrier pickup time: only show deliveries whose label was printed
  # before today's scheduled pickup (i.e. the carrier presumably already took
  # them). ship_labeled_at is now persisted atomically with the state
  # transition into pending_ship_confirm via a before_transition callback in
  # Delivery, so NULL is a real signal of "labels not finalized" and these
  # rows should NOT surface as candidates. If the store has no pickup time
  # configured for this carrier, don't gate at all.
  pickup_time = carrier_pickup_time(carrier)
  if pickup_time
    hw_query = hw_query.ship_labeled_before(pickup_time)
    amz_query = amz_query.ship_labeled_before(pickup_time)
  end

  Delivery.where(id: hw_query.select(:id))
          .or(Delivery.where(id: amz_query.select(:id)))
          .distinct.order(:created_at)
end

#query_recently_shippedActiveRecord::Relation<Delivery>

Returns deliveries shipped or invoiced today or later.

Returns:

  • (ActiveRecord::Relation<Delivery>)

    deliveries shipped or invoiced today or later



531
532
533
534
535
536
537
538
539
540
541
542
# File 'app/models/store.rb', line 531

def query_recently_shipped
  Delivery.by_store_id(id)
          .where(state: %w[shipped invoiced])
          .where(Delivery[:shipped_date].gteq(Date.current))
          .includes(:uploads,
                    :destination_address,
                    :shipping_option,
                    :freight_events,
                    line_items: [:item],
                    order: [:uploads, :creator, { customer: :primary_sales_rep }])
          .order(:created_at)
end

#query_serial_numbers_pending_printActiveRecord::Relation<SerialNumber>

Returns serial numbers awaiting print, ordered by number.

Returns:

  • (ActiveRecord::Relation<SerialNumber>)

    serial numbers awaiting print, ordered by number



526
527
528
# File 'app/models/store.rb', line 526

def query_serial_numbers_pending_print
  serial_numbers.pending_print.order(:number)
end

#query_warehouse_amazon_ordersActiveRecord::Relation<Order>

Returns FBA orders in shippable states.

Returns:

  • (ActiveRecord::Relation<Order>)

    FBA orders in shippable states



509
510
511
512
513
# File 'app/models/store.rb', line 509

def query_warehouse_amazon_orders
  Order.by_store_id(id).limit_to_fba.where(state: %w[in_cr_hold crm_back_order] + Order::SHIPPING_STATES).joins(:deliveries).order(
    Delivery[:future_release_date], Delivery[:created_at]
  )
end

#query_warehouse_back_ordersActiveRecord::Relation<Order>

Returns back orders for this store.

Returns:

  • (ActiveRecord::Relation<Order>)

    back orders for this store



483
484
485
# File 'app/models/store.rb', line 483

def query_warehouse_back_orders
  Order.by_store_id(id).back_order.with_associations
end

#query_warehouse_deliveries(state: nil) ⇒ ActiveRecord::Relation<Delivery>

Returns warehouse deliveries with associations preloaded.

Parameters:

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

    optional delivery state filter

Returns:

  • (ActiveRecord::Relation<Delivery>)

    warehouse deliveries with associations preloaded



496
497
498
499
500
501
502
503
504
505
506
# File 'app/models/store.rb', line 496

def query_warehouse_deliveries(state: nil)
  deliveries = Delivery.by_store_id(id).all_at_warehouse.with_active_parent
                       .includes(:uploads,
                                 :destination_address,
                                 :shipping_option,
                                 :freight_events,
                                 line_items: [:item],
                                 order: [:uploads, :creator, { customer: :primary_sales_rep }])
  deliveries = deliveries.where(state:) if state.present?
  deliveries
end

#query_warehouse_deliveries_groupedHash{String => Array<Delivery>}

Returns warehouse deliveries grouped by state.

Returns:

  • (Hash{String => Array<Delivery>})

    warehouse deliveries grouped by state



516
517
518
# File 'app/models/store.rb', line 516

def query_warehouse_deliveries_grouped
  query_warehouse_deliveries.group_by(&:state)
end

#query_warehouse_deliveries_grouped_countersHash{String => Integer}

Returns warehouse delivery counts by state.

Returns:

  • (Hash{String => Integer})

    warehouse delivery counts by state



521
522
523
# File 'app/models/store.rb', line 521

def query_warehouse_deliveries_grouped_counters
  query_warehouse_deliveries.group('deliveries.state').count
end

#query_warehouse_held_ordersActiveRecord::Relation<Order>

Returns held orders for this store.

Returns:

  • (ActiveRecord::Relation<Order>)

    held orders for this store



473
474
475
# File 'app/models/store.rb', line 473

def query_warehouse_held_orders
  Order.by_store_id(id).held.with_associations
end

#query_warehouse_orders_need_attentionActiveRecord::Relation<Order>

Returns orders with deliveries stuck in quoting.

Returns:

  • (ActiveRecord::Relation<Order>)

    orders with deliveries stuck in quoting



488
489
490
491
492
# File 'app/models/store.rb', line 488

def query_warehouse_orders_need_attention
  Order.by_store_id(id).all_awaiting_deliveries.with_associations.joins(:deliveries).where(Delivery[:state].eq('quoting')).order(
    Delivery[:future_release_date], Delivery[:created_at]
  )
end

#query_warehouse_purchase_orders_shippedActiveRecord::Relation<PurchaseOrder>

Returns shipped purchase orders for this store.

Returns:

  • (ActiveRecord::Relation<PurchaseOrder>)

    shipped purchase orders for this store



545
546
547
# File 'app/models/store.rb', line 545

def query_warehouse_purchase_orders_shipped
  purchase_orders.where(state: 'shipped')
end

#services_accountString

Returns GL account for services (pending fulfillment vs consignment).

Returns:

  • (String)

    GL account for services (pending fulfillment vs consignment)



235
236
237
# File 'app/models/store.rb', line 235

def 
  warmlyyours_warehouse? ? SERVICES_PENDING_FULFILLMENT_DEBIT_ACCOUNT : CONSIGNMENT_ACCOUNT
end

#set_carrier_pickup(carrier, carrier_account_id, pickup_resp, pickup_number, scheduled_start, scheduled_end) ⇒ CarrierPickup

Records a scheduled carrier pickup against this store.

Parameters:

  • carrier (String)

    the carrier key, e.g. "FedExGround"

  • carrier_account_id (Integer)

    the carrier account used

  • pickup_resp (Object)

    the raw carrier API response (logged only)

  • pickup_number (String)

    the carrier's pickup confirmation number

  • scheduled_start (Time)

    pickup window start

  • scheduled_end (Time)

    pickup window end

Returns:



459
460
461
462
463
464
465
466
467
468
469
470
# File 'app/models/store.rb', line 459

def set_carrier_pickup(carrier, , pickup_resp, pickup_number, scheduled_start, scheduled_end)
  Rails.logger.debug { "set_carrier_pickup: carrier: #{carrier}" }
  Rails.logger.debug { "set_carrier_pickup: carrier_account_id: #{}" }
  Rails.logger.debug { "set_carrier_pickup: pickup_resp: #{pickup_resp.inspect}" }
  Rails.logger.debug { "set_carrier_pickup: pickup_number: #{pickup_number}" }
  Rails.logger.debug { "set_carrier_pickup: scheduled_start: #{scheduled_start}" }
  Rails.logger.debug { "set_carrier_pickup: scheduled_end: #{scheduled_end}" }
  pickup = CarrierPickup.new(store_id: id, carrier:, carrier_account_id:, carrier_pickup_id: pickup_number,
                             scheduled_start:, scheduled_end:)
  carrier_pickups << pickup
  pickup
end

#store_items_with_sku_for_selectArray<Array(String, Integer)>

Returns SKU/store-item id pairs ordered by SKU.

Returns:

  • (Array<Array(String, Integer)>)

    SKU/store-item id pairs ordered by SKU



372
373
374
# File 'app/models/store.rb', line 372

def store_items_with_sku_for_select
  store_items.includes(:item).order('items.sku').pluck('items.sku', :id)
end

#time_zone_stringString

Returns the Rails time zone name for this store.

Returns:

  • (String)

    the Rails time zone name for this store



339
340
341
342
343
344
345
# File 'app/models/store.rb', line 339

def time_zone_string
  if country_iso3 == 'CAN'
    'Eastern Time (US & Canada)'
  else
    'America/Chicago'
  end
end

#warmlyyours_warehouse?Boolean

Returns true when this store is a WarmlyYours-owned warehouse.

Returns:

  • (Boolean)

    true when this store is a WarmlyYours-owned warehouse



225
226
227
# File 'app/models/store.rb', line 225

def warmlyyours_warehouse?
  owner == 'warmlyyours'
end