Class: LineItem

Overview

== Schema Information

Table name: line_items
Database name: primary

id :integer not null, primary key
children_count :integer
cm_category :string(255)
description :text
destination_state_code :string(255)
discounted_price :decimal(10, 2) default(0.0), not null
edi_line_number :integer
edi_reference :string
edi_unit_cost :decimal(10, 2)
ignore_tax :boolean
origin_state_code :string(255)
price :decimal(10, 2) not null
qty_shipped :integer
quantity :integer not null
redemption_code :string(255)
resource_type :string(255)
reviewed :boolean default(FALSE), not null
sequence :integer
tax_class :string(255)
tax_only :boolean
tax_rate :decimal(10, 6)
tax_total :decimal(10, 2)
tax_type :string
taxable_amount :decimal(12, 2)
total_cogs :decimal(10, 4)
unit_cogs :decimal(10, 4)
created_at :datetime
updated_at :datetime
backup_catalog_item_id :integer
business_unit_id :integer
catalog_item_id :integer
creator_id :integer
credit_order_line_item_id :integer
credit_rma_item_id :integer
delivery_id :integer
item_id :integer
ledger_company_account_id :integer
legacy_iq_room_line_item_option_group_id :integer
parent_id :integer
replacement_rma_item_id :integer
resource_id :integer
resource_tax_rate_id :integer
room_configuration_id :integer
serial_number_id :integer
shipping_cost_id :integer
store_item_id :integer
updater_id :integer

Indexes

idx_catalog_item_id_delivery_id (catalog_item_id,delivery_id)
idx_delivery_id_item_id_tax_class (delivery_id,item_id,tax_class)
idx_resource_type_resource_id_ci_id (resource_type,resource_id,catalog_item_id)
index_line_items_on_credit_rma_item_id (credit_rma_item_id) WHERE (credit_rma_item_id IS NOT NULL) USING hash
index_line_items_on_delivery_id (delivery_id) WHERE (delivery_id IS NOT NULL) USING hash
index_line_items_on_parent_id (parent_id) USING hash
index_line_items_on_replacement_rma_item_id (replacement_rma_item_id) WHERE (replacement_rma_item_id IS NOT NULL) USING hash
index_line_items_on_resource_type_and_item_id (resource_type,item_id)
index_line_items_on_room_configuration_id (room_configuration_id) WHERE (room_configuration_id IS NOT NULL) USING hash
index_line_items_on_shipping_cost_id (shipping_cost_id) WHERE (shipping_cost_id IS NOT NULL) USING hash
line_items_delivery_id_dup_idx (resource_type,resource_id,item_id,delivery_id) UNIQUE WHERE ((tax_class)::text = 'shp'::text)
line_items_resource_id_index (resource_id)

Foreign Keys

fk_rails_... (catalog_item_id => catalog_items.id)
line_items_catalog_item_id_fk (catalog_item_id => catalog_items.id)
line_items_delivery_id_fk (delivery_id => deliveries.id) ON DELETE => nullify
line_items_room_configuration_id_fk (room_configuration_id => room_configurations.id) ON DELETE => nullify
line_items_shipping_costs_id_fk (shipping_cost_id => shipping_costs.id) ON DELETE => cascade

Constant Summary collapse

LINE_ITEM_TAX_CLASSES =

Line item tax classes.

{ g: 'GOODS', svc: 'SERVICE', shp: 'FREIGHT', none: 'NONE' }.freeze
LINE_ITEM_TAX_CLASSES_SORT_ORDER =

Line item tax classes sort order.

%i[g svc shp none].freeze

Constants included from Models::InventoryCommittable

Models::InventoryCommittable::STATES_WITH_NO_EXPIRATION

Constants included from Models::Auditable

Models::Auditable::ALWAYS_IGNORED

Constants included from Models::Schedulable

Models::Schedulable::SIMPLE_FORM_OPTIONS

Instance Attribute Summary collapse

Attributes included from Models::TaxableLine

#do_not_calculate_tax

Belongs to collapse

Methods included from Models::TaxableLine

#destination_state, #origin_state, #resource_tax_rate

Methods included from Models::Auditable

#creator, #updater

Has one collapse

Has many collapse

Methods included from Models::Reservable

#reserved_serial_numbers

Delegated Instance Attributes collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Models::Lineage

#ancestors, #ancestors_ids, #children_and_roots, #descendants, #descendants_ids, #ensure_non_recursive_lineage, #family_members, #generate_full_name, #generate_full_name_array, #lineage, #lineage_array, #lineage_simple, #root, #root_id, #self_ancestors_and_descendants, #self_ancestors_and_descendants_ids, #self_and_ancestors, #self_and_ancestors_ids, #self_and_children, #self_and_descendants, #self_and_descendants_ids, #self_and_siblings, #self_and_siblings_ids, #siblings, #siblings_ids

Methods included from Models::TaxableLine

#calculate_tax, #has_tax?, #is_international?, #set_initial_tax_rate, #update_tax_rate

Methods included from Models::InventoryCommittable

#can_be_committed?, #can_be_uncommitted?, #commit_line_items, #determine_commit_expiration_date, #has_committed_line_items?, #uncommit_line_items

Methods included from Models::Reservable

#all_reserved_serial_numbers, #all_reserved_serial_numbers_available?, #auto_reserve_serial_numbers, #available_serial_numbers, #commit_reserved_serial_numbers, #fully_reserved?, #kit_components_requires_reservation?, #link_serial_numbers, #rejoin_serial_numbers, #require_reservation?, #reservable_item, #reservable_store_item, #serial_numbers, #show_serial_number_toggle?, #skip_reservation_screen?, #split_serial_numbers, #total_reserved_serial_numbers, #uncommit_reserved_serial_numbers, #unlink_serial_numbers, #update_serial_numbers_shipped_count

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

#business_unit_idObject (readonly)

Ledger company account id.

Validations:

  • Presence ({ if: proc { |li| (li.cm_category == 'Misc') && li.linked_to_standalone_credit_memo } })


159
# File 'app/models/line_item.rb', line 159

validates :ledger_company_account_id, :business_unit_id, presence: { if: proc { |li| (li.cm_category == 'Misc') && li.linked_to_standalone_credit_memo } }

#cm_categoryObject (readonly)

Cm category.

Validations:

  • Presence ({ if: proc { |li| li.linked_to_credit_memo || li.linked_to_invoice } })
  • Inclusion ({ in: CreditMemo::LINE_ITEM_CATEGORIES.pluck(:name), message: 'is not a valid category', if: :linked_to_credit_memo })
  • Inclusion ({ in: Invoice::LINE_ITEM_CATEGORIES.pluck(:name), message: 'is not a valid category', if: :linked_to_invoice })


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

validates :cm_category, presence: { if: proc { |li| li.linked_to_credit_memo || li.linked_to_invoice } }

#discounted_priceObject (readonly)

Discounted price.

Validations:

  • Numericality ({ equal_to: 0, if: :tax_only?, message: 'must be equal to 0 for tax only lines' })


171
# File 'app/models/line_item.rb', line 171

validates :discounted_price, numericality: { equal_to: 0, if: :tax_only?, message: 'must be equal to 0 for tax only lines' }

#force_catalog_validationObject

Returns the value of attribute force_catalog_validation.



261
262
263
# File 'app/models/line_item.rb', line 261

def force_catalog_validation
  @force_catalog_validation
end

#item_idObject (readonly)

Item id.

Validations:

  • Presence ({ if: proc { |li| (li.cm_category == 'Item') && li.linked_to_standalone_invoice } })


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

validates :item_id, presence: { if: proc { |li| (li.cm_category == 'Item') && li.linked_to_standalone_invoice } }

#item_nameObject

Returns the value of attribute item_name.



261
262
263
# File 'app/models/line_item.rb', line 261

def item_name
  @item_name
end

#ledger_company_account_idObject (readonly)

Ledger company account id.

Validations:

  • Presence ({ if: proc { |li| (li.cm_category == 'Misc') && li.linked_to_standalone_credit_memo } })
  • Presence ({ if: proc { |li| (li.cm_category == 'Misc') && li.linked_to_invoice } })


159
# File 'app/models/line_item.rb', line 159

validates :ledger_company_account_id, :business_unit_id, presence: { if: proc { |li| (li.cm_category == 'Misc') && li.linked_to_standalone_credit_memo } }

#original_delivery_idObject

Returns the value of attribute original_delivery_id.



261
262
263
# File 'app/models/line_item.rb', line 261

def original_delivery_id
  @original_delivery_id
end

#qty_availableObject

Returns the value of attribute qty_available.



261
262
263
# File 'app/models/line_item.rb', line 261

def qty_available
  @qty_available
end

#quantityObject (readonly)

Quantity.

Validations:



168
# File 'app/models/line_item.rb', line 168

validates :quantity, presence: true

#sku_overrideObject

Returns the value of attribute sku_override.



261
262
263
# File 'app/models/line_item.rb', line 261

def sku_override
  @sku_override
end

#tax_classObject (readonly)

Tax class.

Validations:

  • Presence ({ if: :linked_to_standalone_credit_memo_or_invoice })
  • Inclusion ({ in: ['shp'], message: 'must be set to Freight for freight coupons', if: proc { |li| li.cm_category == 'Coupon (Freight)' } })


161
# File 'app/models/line_item.rb', line 161

validates :tax_class, :taxable_amount, presence: { if: :linked_to_standalone_credit_memo_or_invoice }

#taxable_amountObject (readonly)

Tax class.

Validations:

  • Presence ({ if: :linked_to_standalone_credit_memo_or_invoice })
  • Numericality ({ greater_than_or_equal_to: 0, if: :linked_to_standalone_credit_memo_or_invoice })


161
# File 'app/models/line_item.rb', line 161

validates :tax_class, :taxable_amount, presence: { if: :linked_to_standalone_credit_memo_or_invoice }

#unit_cogsObject (readonly)

Unit cogs.

Validations:

  • Presence ({ if: proc { |li| (li.cm_category == 'Item') && li.linked_to_invoice } })


165
# File 'app/models/line_item.rb', line 165

validates :unit_cogs, presence: { if: proc { |li| (li.cm_category == 'Item') && li.linked_to_invoice } }

Class Method Details

.accessoriesActiveRecord::Relation<LineItem>

A relation of LineItems that are accessories. Active Record Scope

Returns:

See Also:



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

scope :accessories, -> { joins(:item).merge(Item.accessories) }

.availability_hash(line_items) ⇒ Object

Availability hash.

Parameters:

  • line_items (Object)

Returns:

  • (Object)


681
682
683
684
685
686
687
688
689
690
691
692
# File 'app/models/line_item.rb', line 681

def self.availability_hash(line_items)
  result = {}
  line_items.each do |li|
    next unless li.catalog_item

    cat_item_id = li.catalog_item.id
    result[cat_item_id] ||= { 'available' => li.catalog_item.qty_available, 'ordered' => 0, 'in_stock' => li.catalog_item.qty_available.positive? }
    result[cat_item_id]['ordered'] += li.quantity
    result[cat_item_id]['in_stock'] = false if result[cat_item_id]['ordered'] > result[cat_item_id]['available']
  end
  result
end

.bstockActiveRecord::Relation<LineItem>

A relation of LineItems that are bstock. Active Record Scope

Returns:

See Also:



249
# File 'app/models/line_item.rb', line 249

scope :bstock, -> { joins(:item).where(items: { condition: 'refurbished' }) }

.by_product_category_pathActiveRecord::Relation<LineItem>

A relation of LineItems that are by product category path. Active Record Scope

Returns:

See Also:



232
# File 'app/models/line_item.rb', line 232

scope :by_product_category_path, ->(slug_path) { joins(:item).merge(Item.by_product_category_path(slug_path)) }

.by_product_category_urlActiveRecord::Relation<LineItem>

A relation of LineItems that are by product category url. Active Record Scope

Returns:

See Also:



228
# File 'app/models/line_item.rb', line 228

scope :by_product_category_url, ->(url) { joins(:item).merge(Item.by_product_category_url(url)) }

.by_product_line_pathActiveRecord::Relation<LineItem>

A relation of LineItems that are by product line path. Active Record Scope

Returns:

See Also:



231
# File 'app/models/line_item.rb', line 231

scope :by_product_line_path, ->(slug_path) { joins(:item).merge(Item.by_product_line_path(slug_path)) }

.by_product_line_urlActiveRecord::Relation<LineItem>

A relation of LineItems that are by product line url. Active Record Scope

Returns:

See Also:



229
# File 'app/models/line_item.rb', line 229

scope :by_product_line_url, ->(url) { joins(:item).merge(Item.by_product_line_url(url)) }

.cold_leadsActiveRecord::Relation<LineItem>

A relation of LineItems that are cold leads. Active Record Scope

Returns:

See Also:



214
# File 'app/models/line_item.rb', line 214

scope :cold_leads, -> { joins(:item).merge(Item.cold_leads) }

.collect_product_lines(line_items) ⇒ Object

Collect product lines.

Parameters:

  • line_items (Object)

Returns:

  • (Object)


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

def self.collect_product_lines(line_items)
  line_items.filter_map { |li| li&.item&.product_lines }.flatten.uniq
end

.compare(source_line_items, target_line_items) ⇒ Object

Compare.

Parameters:

  • source_line_items (Object)
  • target_line_items (Object)

Returns:

  • (Object)


477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
# File 'app/models/line_item.rb', line 477

def self.compare(source_line_items, target_line_items)
  report = {}
  all_matched_line_ids = []
  source_line_items.each do |source_line|
    matched_lines_in_target = target_line_items.select { |target_line| target_line.catalog_item_id == source_line.catalog_item_id }
    if matched_lines_in_target.empty?
      report[source_line] = { status: :not_found, note: 'No matching lines found in target', matching_line_ids: [] }
    else
      matched_line_ids = matched_lines_in_target.map(&:id)
      all_matched_line_ids += matched_line_ids
      report[source_line] = { status: :ok, note: "Found similar line at line id #{matched_line_ids.join(',')}", matching_line_ids: matched_line_ids }
    end
  end
  # Anything left?
  leftover_line_ids = target_line_items.reject { |tl| all_matched_line_ids.include? tl.id }.map(&:id)
  report['extra_lines'] = { status: :extra, note: "Found extra lines id #{leftover_line_ids.join(',')}", matching_line_ids: leftover_line_ids } unless leftover_line_ids.empty?
  report
end

.controlsActiveRecord::Relation<LineItem>

A relation of LineItems that are controls. Active Record Scope

Returns:

See Also:



196
# File 'app/models/line_item.rb', line 196

scope :controls, -> { joins(:item).merge(Item.controls) }

.countertop_heatersActiveRecord::Relation<LineItem>

A relation of LineItems that are countertop heaters. Active Record Scope

Returns:

See Also:



227
# File 'app/models/line_item.rb', line 227

scope :countertop_heaters, -> { joins(:item).merge(Item.countertop_heaters) }

.custom_matsActiveRecord::Relation<LineItem>

A relation of LineItems that are custom mats. Active Record Scope

Returns:

See Also:



226
# File 'app/models/line_item.rb', line 226

scope :custom_mats, -> { joins(:item).merge(Item.custom_mats) }

.dropshipActiveRecord::Relation<LineItem>

A relation of LineItems that are dropship. Active Record Scope

Returns:

See Also:



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

scope :dropship, -> { joins(:item).merge(Item.dropships) }

.easystatsActiveRecord::Relation<LineItem>

A relation of LineItems that are easystats. Active Record Scope

Returns:

See Also:



204
# File 'app/models/line_item.rb', line 204

scope :easystats, -> { joins(:item).merge(Item.easystats) }

.electrical_plan_controlsActiveRecord::Relation<LineItem>

A relation of LineItems that are electrical plan controls. Active Record Scope

Returns:

See Also:



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

scope :electrical_plan_controls, -> { joins(:item).merge(Item.electrical_plan_controls) }

.floor_heating_elementsActiveRecord::Relation<LineItem>

A relation of LineItems that are floor heating elements. Active Record Scope

Returns:

See Also:



193
# File 'app/models/line_item.rb', line 193

scope :floor_heating_elements, -> { joins(:item).merge(Item.floor_heating_elements) }

.goodsActiveRecord::Relation<LineItem>

A relation of LineItems that are goods. Active Record Scope

Returns:

See Also:



217
# File 'app/models/line_item.rb', line 217

scope :goods,    -> { where(tax_class: 'g') }

.group_by_category(line_items) ⇒ Object

Group by category.

Parameters:

  • line_items (Object)

Returns:

  • (Object)


382
383
384
385
386
387
388
389
390
391
# File 'app/models/line_item.rb', line 382

def self.group_by_category(line_items)
  line_items = line_items.to_a.sort_by(&:sort_priority)
  all_items = line_items.group_by { |li| li.item&.product_category_name || 'Not Categorized' }

  # Use cached priorities from ProductCategoryConstants
  all_items.sort_by do |_name, items|
    category_id = items.first&.item&.product_category_id
    ProductCategoryConstants.priority_for(category_id)
  end
end

.has_floor_sensorActiveRecord::Relation<LineItem>

A relation of LineItems that are has floor sensor. Active Record Scope

Returns:

See Also:



220
# File 'app/models/line_item.rb', line 220

scope :has_floor_sensor, -> { joins(:item).merge(Item.has_floor_sensor) }

.heating_elementsActiveRecord::Relation<LineItem>

A relation of LineItems that are heating elements. Active Record Scope

Returns:

See Also:



190
# File 'app/models/line_item.rb', line 190

scope :heating_elements, -> { joins(:item).merge(Item.heating_elements) }

.install_kitsActiveRecord::Relation<LineItem>

A relation of LineItems that are install kits. Active Record Scope

Returns:

See Also:



223
# File 'app/models/line_item.rb', line 223

scope :install_kits, -> { joins(:item).merge(Item.install_kits) }

.insulationsActiveRecord::Relation<LineItem>

A relation of LineItems that are insulations. Active Record Scope

Returns:

See Also:



224
# File 'app/models/line_item.rb', line 224

scope :insulations, -> { joins(:item).merge(Item.insulations) }

.integration_kitsActiveRecord::Relation<LineItem>

A relation of LineItems that are integration kits. Active Record Scope

Returns:

See Also:



206
# File 'app/models/line_item.rb', line 206

scope :integration_kits, -> { joins(:item).merge(Item.integration_kits) }

.inventory_check(container) ⇒ Object

Inventory check.

Parameters:

  • container (Object)

Returns:

  • (Object)


316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
# File 'app/models/line_item.rb', line 316

def self.inventory_check(container)
  lines = container.line_items.non_shipping
                   .includes(:item, :direct_store_item, :inventory_commits, { catalog_item: :store_item })

  # Per-line check: handles dropship, unlimited inventory, fully-committed lines, negative qty.
  return :none unless lines.all?(&:is_fulfillable?)

  # Aggregate check: multiple non-shipping lines targeting the same SKU — whether duplicate
  # standalone lines or kit component children overlapping with standalone lines — must not
  # together exceed the shared qty_available pool. The per-line check misses this because
  # each line independently sees the full pool without deducting the other lines' demand.
  #
  # Cache from_store_id once: all lines share the same resource (container). This mirrors
  # the stock_status StoreItem routing (line_item.rb#stock_status) without an association
  # load per line.
  from_store_id = container.respond_to?(:from_store_id) ? container.from_store_id : nil

  # Key demand and store_items by store_item ID so that all lines drawing from the same
  # physical stock pool are always aggregated together, regardless of which catalog_item
  # they arrived through. This also handles from_store_id and cross-catalog edge cases.
  uncommitted_demand = Hash.new(0)
  store_item_by_id   = {}

  lines.each do |li|
    next unless li.catalog_item_id
    next if li.quantity.negative?
    # Route through the per-line helper so that RoomConfiguration resources (which always
    # return false from single_origin_or_warehouse_pickup?) are handled identically to the
    # per-line check. In practice, container.line_items only returns Order-resource lines,
    # but this keeps the logic consistent with stock_status.
    next if li.dropship? && !li.single_origin_or_warehouse_pickup?
    next if li.unlimited_inventory?

    committed_quantity = li.inventory_commits.sum(&:quantity)
    # Already fully reserved — committed units have already reduced qty_available globally.
    next if committed_quantity >= li.quantity

    # Add only the truly uncommitted portion. Committed units are already subtracted from
    # qty_available globally, so adding the full quantity would double-count the committed
    # share against the same pool it was already deducted from.
    uncommitted_qty = li.quantity - committed_quantity

    # Mirror stock_status's StoreItem routing so the aggregate compares against the correct
    # store's pool. For store-transfer orders (from_store_id set), this fires one SQL query
    # per line — consistent with the existing per-line behavior in stock_status.
    si = if from_store_id
           StoreItem.where(store_id: from_store_id, item_id: li.item_id, location: 'AVAILABLE').first
         else
           li.store_item
         end
    next unless si

    uncommitted_demand[si.id] += uncommitted_qty
    store_item_by_id[si.id] ||= si
  end

  store_item_by_id.each do |si_id, si|
    return :none if si.qty_available < uncommitted_demand[si_id]
  end

  :ok
end

.kitsActiveRecord::Relation<LineItem>

A relation of LineItems that are kits. Active Record Scope

Returns:

See Also:



243
# File 'app/models/line_item.rb', line 243

scope :kits, -> { joins(:item).merge(Item.kits) }

.membranesActiveRecord::Relation<LineItem>

A relation of LineItems that are membranes. Active Record Scope

Returns:

See Also:



225
# File 'app/models/line_item.rb', line 225

scope :membranes, -> { joins(:item).merge(Item.membranes) }

.must_be_shipped_insuredActiveRecord::Relation<LineItem>

A relation of LineItems that are must be shipped insured. Active Record Scope

Returns:

See Also:



233
# File 'app/models/line_item.rb', line 233

scope :must_be_shipped_insured, -> { with_associations.where('store_items.must_be_shipped_insured IS TRUE') }

.no_tax_classActiveRecord::Relation<LineItem>

A relation of LineItems that are no tax class. Active Record Scope

Returns:

See Also:



219
# File 'app/models/line_item.rb', line 219

scope :no_tax_class, -> { where(tax_class: 'none') }

.non_dropshipActiveRecord::Relation<LineItem>

A relation of LineItems that are non dropship. Active Record Scope

Returns:

See Also:



209
# File 'app/models/line_item.rb', line 209

scope :non_dropship, -> { joins(:item).merge(Item.non_dropships) }

.non_shippingActiveRecord::Relation<LineItem>

A relation of LineItems that are non shipping. Active Record Scope

Returns:

See Also:



215
# File 'app/models/line_item.rb', line 215

scope :non_shipping, -> { where(tax_class: %w[g svc none]) }

.oj_programmable_tstatsActiveRecord::Relation<LineItem>

A relation of LineItems that are oj programmable tstats. Active Record Scope

Returns:

See Also:



202
# File 'app/models/line_item.rb', line 202

scope :oj_programmable_tstats, -> { joins(:item).merge(Item.oj_programmable_tstats) }

.oj_tstatsActiveRecord::Relation<LineItem>

A relation of LineItems that are oj tstats. Active Record Scope

Returns:

See Also:



201
# File 'app/models/line_item.rb', line 201

scope :oj_tstats, -> { joins(:item).merge(Item.oj_tstats) }

.order_by_specActiveRecord::Relation<LineItem>

A relation of LineItems that are order by spec. Active Record Scope

Returns:

See Also:



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

scope :order_by_spec, ->(spec, datatype = 'varchar', direction = 'asc') { joins(:item).merge(Item.order_by_spec(spec, datatype, direction)) }

.power_modulesActiveRecord::Relation<LineItem>

A relation of LineItems that are power modules. Active Record Scope

Returns:

See Also:



205
# File 'app/models/line_item.rb', line 205

scope :power_modules, -> { joins(:item).merge(Item.power_modules) }

.powersActiveRecord::Relation<LineItem>

A relation of LineItems that are powers. Active Record Scope

Returns:

See Also:



197
# File 'app/models/line_item.rb', line 197

scope :powers, -> { joins(:item).merge(Item.powers) }

.product_category_sortedActiveRecord::Relation<LineItem>

A relation of LineItems that are product category sorted. Active Record Scope

Returns:

See Also:



188
# File 'app/models/line_item.rb', line 188

scope :product_category_sorted, -> { includes(item: :product_category).joins(item: :product_category).order(ProductCategory[:priority]) }

.relay_panelsActiveRecord::Relation<LineItem>

A relation of LineItems that are relay panels. Active Record Scope

Returns:

See Also:



211
# File 'app/models/line_item.rb', line 211

scope :relay_panels, -> { joins(:item).merge(Item.relay_panels) }

.reservableActiveRecord::Relation<LineItem>

A relation of LineItems that are reservable. Active Record Scope

Returns:

See Also:



221
# File 'app/models/line_item.rb', line 221

scope :reservable, -> { joins(:item).merge(Item.reservable) }

.rough_in_kitsActiveRecord::Relation<LineItem>

A relation of LineItems that are rough in kits. Active Record Scope

Returns:

See Also:



222
# File 'app/models/line_item.rb', line 222

scope :rough_in_kits, -> { joins(:item).merge(Item.rough_in_kits) }

.sensorsActiveRecord::Relation<LineItem>

A relation of LineItems that are sensors. Active Record Scope

Returns:

See Also:



207
# File 'app/models/line_item.rb', line 207

scope :sensors, -> { joins(:item).merge(Item.sensors) }

.servicesActiveRecord::Relation<LineItem>

A relation of LineItems that are services. Active Record Scope

Returns:

See Also:



218
# File 'app/models/line_item.rb', line 218

scope :services, -> { where(tax_class: 'svc') }

.shipping_onlyActiveRecord::Relation<LineItem>

A relation of LineItems that are shipping only. Active Record Scope

Returns:

See Also:



216
# File 'app/models/line_item.rb', line 216

scope :shipping_only, -> { where(tax_class: 'shp') }

.sku_and_aliases_searchActiveRecord::Relation<LineItem>

A relation of LineItems that are sku and aliases search. Active Record Scope

Returns:

See Also:



250
# File 'app/models/line_item.rb', line 250

scope :sku_and_aliases_search, ->(sku) { joins(:item).merge(Item.sku_and_aliases_search(sku)) }

.smartfit_itemsActiveRecord::Relation<LineItem>

A relation of LineItems that are smartfit items. Active Record Scope

Returns:

See Also:



238
# File 'app/models/line_item.rb', line 238

scope :smartfit_items, -> { by_product_line_path(LtreePaths::PL_SERVICES_SMARTFIT) }

.smartfix_itemsActiveRecord::Relation<LineItem>

A relation of LineItems that are smartfix items. Active Record Scope

Returns:

See Also:



240
# File 'app/models/line_item.rb', line 240

scope :smartfix_items, -> { by_product_line_path(LtreePaths::PL_SERVICES_SMARTFIX) }

.smartguide_itemsActiveRecord::Relation<LineItem>

A relation of LineItems that are smartguide items. Active Record Scope

Returns:

See Also:



241
# File 'app/models/line_item.rb', line 241

scope :smartguide_items, -> { by_product_line_path(LtreePaths::PL_SERVICES_SMARTGUIDE) }

.smartinstall_itemsActiveRecord::Relation<LineItem>

A relation of LineItems that are smartinstall items. Active Record Scope

Returns:

See Also:



239
# File 'app/models/line_item.rb', line 239

scope :smartinstall_items, -> { by_product_line_path(LtreePaths::PL_SERVICES_SMARTINSTALL) }

.smartpresetsActiveRecord::Relation<LineItem>

A relation of LineItems that are smartpresets. Active Record Scope

Returns:

See Also:



213
# File 'app/models/line_item.rb', line 213

scope :smartpresets, -> { joins(:item).merge(Item.smartpresets) }

.smartservice_itemsActiveRecord::Relation<LineItem>

A relation of LineItems that are smartservice items. Active Record Scope

Returns:

See Also:



242
# File 'app/models/line_item.rb', line 242

scope :smartservice_items, -> { joins(:item).merge(Item.smart_services) }

.smartstatsActiveRecord::Relation<LineItem>

A relation of LineItems that are smartstats. Active Record Scope

Returns:

See Also:



203
# File 'app/models/line_item.rb', line 203

scope :smartstats, -> { joins(:item).merge(Item.smartstats) }

.snow_melting_elementsActiveRecord::Relation<LineItem>

A relation of LineItems that are snow melting elements. Active Record Scope

Returns:

See Also:



194
# File 'app/models/line_item.rb', line 194

scope :snow_melting_elements, -> { joins(:item).merge(Item.snow_melting_elements) }

.sort_for_planActiveRecord::Relation<LineItem>

A relation of LineItems that are sort for plan. Active Record Scope

Returns:

See Also:



199
# File 'app/models/line_item.rb', line 199

scope :sort_for_plan, -> { order_by_spec('amps', 'decimal', 'asc') }

.sort_for_room_display(line_items) ⇒ Object

Customer-facing room ordering for the online quote (/ql/) and the per-room
breakdown: heating element(s) first, then thermostat(s), then accessories —
each bucket sorted by line total high → low (most expensive first). Items
with no known category fall to the accessories bucket. Pure in-memory sort
on an already-loaded collection.

Parameters:

  • line_items (Object)

Returns:

  • (Object)


400
401
402
403
404
405
406
407
408
409
# File 'app/models/line_item.rb', line 400

def self.sort_for_room_display(line_items)
  line_items.to_a.each_with_index.sort_by do |li, idx|
    item = li.item || li.catalog_item&.item
    bucket = if item&.is_heating_element? then 0
             elsif item&.is_thermostat? then 1
             else 2
             end
    [bucket, -(li.total || 0).to_f, idx] # idx keeps the sort stable on ties
  end.map(&:first)
end

.tempzone_onlyActiveRecord::Relation<LineItem>

A relation of LineItems that are tempzone only. Active Record Scope

Returns:

See Also:



200
# File 'app/models/line_item.rb', line 200

scope :tempzone_only, -> { joins(:item).merge(Item.tempzones) }

.tempzone_or_environ_heating_elementsActiveRecord::Relation<LineItem>

A relation of LineItems that are tempzone or environ heating elements. Active Record Scope

Returns:

See Also:



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

scope :tempzone_or_environ_heating_elements, -> {
  heating_elements.joins(:item).where(Item[:primary_pl_path_slugs].ltree_descendant(LtreePaths::PL_FLOOR_HEATING_TEMPZONE))
                  .or(heating_elements.joins(:item).where(Item[:primary_pl_path_slugs].ltree_descendant(LtreePaths::PL_FLOOR_HEATING_ENVIRON)))
}

.thermostatsActiveRecord::Relation<LineItem>

A relation of LineItems that are thermostats. Active Record Scope

Returns:

See Also:



210
# File 'app/models/line_item.rb', line 210

scope :thermostats, -> { joins(:item).merge(Item.thermostats) }

.total_of_collection(line_items) ⇒ Object

Total of collection.

Parameters:

  • line_items (Object)

Returns:

  • (Object)


309
310
311
# File 'app/models/line_item.rb', line 309

def self.total_of_collection(line_items)
  line_items.to_a.sum(&:total).round(2)
end

.underlaymentsActiveRecord::Relation<LineItem>

A relation of LineItems that are underlayments. Active Record Scope

Returns:

See Also:



212
# File 'app/models/line_item.rb', line 212

scope :underlayments, -> { joins(:item).merge(Item.underlayments) }

.with_associationsActiveRecord::Relation<LineItem>

A relation of LineItems that are with associations. Active Record Scope

Returns:

See Also:



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

scope :with_associations, -> { includes(:item, catalog_item: { store_item: :item }).references(:item, catalog_item: { store_item: :item }) }

.with_positive_qtyActiveRecord::Relation<LineItem>

A relation of LineItems that are with positive qty. Active Record Scope

Returns:

See Also:



248
# File 'app/models/line_item.rb', line 248

scope :with_positive_qty, -> { where('quantity > 0') }

.with_product_specificationActiveRecord::Relation<LineItem>

A relation of LineItems that are with product specification. Active Record Scope

Returns:

See Also:



191
# File 'app/models/line_item.rb', line 191

scope :with_product_specification, ->(token, value, grouping = nil) { joins(:item).merge(Item.with_product_specification(token, value, grouping)) }

.with_reserved_serial_numbers_countActiveRecord::Relation<LineItem>

A relation of LineItems that are with reserved serial numbers count. Active Record Scope

Returns:

See Also:



247
# File 'app/models/line_item.rb', line 247

scope :with_reserved_serial_numbers_count, -> { all.select_append('(select count(rsn.id) from reserved_serial_numbers rsn where rsn.line_item_id = line_items.id) as reserved_serial_numbers_count') }

.with_serial_numbers_countActiveRecord::Relation<LineItem>

A relation of LineItems that are with serial numbers count. Active Record Scope

Returns:

See Also:



246
# File 'app/models/line_item.rb', line 246

scope :with_serial_numbers_count, -> { all.select_append('(select count(sn.id) from serial_numbers sn where sn.line_item_id = line_items.id) as serial_numbers_count') }

.without_childrenActiveRecord::Relation<LineItem>

A relation of LineItems that are without children. Active Record Scope

Returns:

See Also:



244
# File 'app/models/line_item.rb', line 244

scope :without_children, -> { where(children_count: [nil, 0]) }

Instance Method Details

#all_quantities_allocated_to_shipments?Boolean

Whether all quantities allocated to shipments.

Returns:

  • (Boolean)


509
510
511
# File 'app/models/line_item.rb', line 509

def all_quantities_allocated_to_shipments?
  remaining_quantity_to_allocate_to_shipments == 0
end

#as_jsonObject

As json.

Returns:

  • (Object)


1072
1073
1074
# File 'app/models/line_item.rb', line 1072

def as_json
  super(methods: %i[name sku get_item_id])
end

#business_unitBusinessUnit

Business unit.



132
# File 'app/models/line_item.rb', line 132

belongs_to :business_unit, optional: true

#calculated_tax_classObject

Calculated tax class.

Returns:

  • (Object)


565
566
567
568
569
# File 'app/models/line_item.rb', line 565

def calculated_tax_class
  return tax_class if tax_class

  item.nil? ? tax_class : item.tax_class
end

#catalog_itemCatalogItem

Catalog item.



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

belongs_to :catalog_item, inverse_of: :line_items, optional: true

#category_nameObject

Alias for Item#category_name

Returns:

  • (Object)

    Item#category_name

See Also:



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'app/models/line_item.rb', line 265

delegate :is_oj_programmable_tstat?,
:is_smartstat?,
:is_cork?,
:is_relay_panel?,
:is_legacy_relay?,
:is_publication?,
:maximum_current,
:num_poles,
:category_name,
:image_path,
:image_url,
:thumbnail_url,
:is_floor_heating_product?,
:oversize?,
:ships_via_freight?,
:product_tax_code,
:is_snow_melting_product?,
:is_reviewable?,
:product_line_for_review,
to: :item,
allow_nil: true

#children_description(short = false, with_brackets = false) ⇒ Object

Children description.

Parameters:

  • short (Boolean) (defaults to: false)
  • with_brackets (Boolean) (defaults to: false)

Returns:

  • (Object)


787
788
789
790
791
792
793
794
795
796
797
# File 'app/models/line_item.rb', line 787

def children_description(short = false, with_brackets = false)
  contents = nil
  if children.any?
    contents = ''
    contents += "The #{sku} is " unless short
    contents += 'composed of '
    contents += children.map { |child| "#{child.quantity} x #{child.item.sku}" }.to_sentence(last_word_connector: ' and ')
    contents = "(#{contents})" if with_brackets
  end
  contents
end

#cogs_markupObject

Cogs markup.

Returns:

  • (Object)


934
935
936
# File 'app/models/line_item.rb', line 934

def cogs_markup
  1.1 # 10% markup for packaging and unapplied landed cost
end

#company_account_refObject

Company account ref.

Returns:

  • (Object)


559
560
561
# File 'app/models/line_item.rb', line 559

def 
  &.identifier
end

#copy_discounted_priceObject (protected)

Copy discounted price.

Returns:

  • (Object)


1170
1171
1172
# File 'app/models/line_item.rb', line 1170

def copy_discounted_price
  self.price = discounted_price
end

#coupon_amountObject

Coupon amount.

Returns:

  • (Object)


503
504
505
# File 'app/models/line_item.rb', line 503

def coupon_amount
  line_discounts.sum(:amount)
end

#credit_memoCreditMemo

Credit memo.

Returns:

See Also:



111
# File 'app/models/line_item.rb', line 111

belongs_to :credit_memo, -> { joins(:line_items).where(line_items: { resource_type: 'CreditMemo' }) }, foreign_key: :resource_id, optional: true

#credit_order_line_itemLineItem

Credit order line item.

Returns:

See Also:



126
# File 'app/models/line_item.rb', line 126

belongs_to :credit_order_line_item, class_name: 'LineItem', optional: true

#credit_percentageObject

Credit percentage.

Returns:

  • (Object)


579
580
581
582
583
584
585
586
587
# File 'app/models/line_item.rb', line 579

def credit_percentage
  return nil if credit_rma_item.nil? && credit_order_line_item.nil?

  if credit_rma_item
    credit_rma_item.credit_percentage
  elsif credit_order_line_item.try(:credit_rma_item)
    credit_order_line_item.credit_rma_item.credit_percentage
  end
end

#credit_rma_itemRmaItem

Credit rma item.

Returns:

See Also:



122
# File 'app/models/line_item.rb', line 122

belongs_to :credit_rma_item, class_name: 'RmaItem', optional: true

#currencyObject

Delegators



497
498
499
# File 'app/models/line_item.rb', line 497

def currency
  resource.try(:currency)
end

#currency_symbolObject

Currency symbol.

Returns:

  • (Object)


801
802
803
# File 'app/models/line_item.rb', line 801

def currency_symbol
  (resource || delivery).currency_symbol
end

#current_line_cogsObject

Current line cogs.

Returns:

  • (Object)


917
918
919
920
921
922
923
924
925
926
927
928
929
930
# File 'app/models/line_item.rb', line 917

def current_line_cogs
  # Catalog cogs first
  uc = unit_cogs if unit_cogs && unit_cogs > 0
  uc ||= catalog_item.try(:unit_cogs)
  uc ||= begin
    SupplierItem.order(:id).reverse_order.find_by(item_id:).current_price.unit_cost * cogs_markup
  rescue StandardError
    nil
  end

  return if uc.nil?

  uc * quantity
end

#current_line_profitObject

Current line profit.

Returns:

  • (Object)


950
951
952
953
954
955
956
# File 'app/models/line_item.rb', line 950

def current_line_profit
  if estimated_line_cost && (dt = discounted_total)
    dt - estimated_line_cost
  else
    BigDecimal('0.00')
  end
end

#current_line_profit_marginObject

To find the margin, divide gross profit by the revenue.



968
969
970
971
972
973
974
975
# File 'app/models/line_item.rb', line 968

def current_line_profit_margin
  gross_profit = current_line_profit
  revenue = discounted_total
  return 0.0 if gross_profit == 0
  return -1.0 if revenue == 0

  gross_profit / revenue
end

#current_line_profit_markupObject

To write the markup as a percentage, divide the gross profit by the COGS.



959
960
961
962
963
964
965
# File 'app/models/line_item.rb', line 959

def current_line_profit_markup
  cogs = estimated_line_cost
  gross_profit = current_line_profit
  return 0.0 if cogs == 0

  gross_profit / cogs
end

#deep_dupObject

Deep dup.

Returns:

  • (Object)


89
90
91
# File 'app/models/line_item.rb', line 89

def deep_dup
  deep_clone(include: %i[serial_numbers line_discounts])
end

#deliveryDelivery

Delivery.

Returns:

See Also:



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

belongs_to :delivery, inverse_of: :line_items, optional: true

#detail_urlObject

Detail url.

Returns:

  • (Object)


1158
1159
1160
1161
1162
1163
1164
# File 'app/models/line_item.rb', line 1158

def detail_url
  i = item
  return unless i&.canonical_path

  locale = resource.try(:store_id) == 2 ? 'en-CA' : 'en-US'
  "https://#{WEB_HOSTNAME}#{i.canonical_url(locale: locale)}"
end

#direct_store_itemStoreItem

Direct store item.

Returns:

See Also:



114
# File 'app/models/line_item.rb', line 114

belongs_to :direct_store_item, class_name: 'StoreItem', foreign_key: :store_item_id, optional: true

#discounted_totalObject

Discounted total.

Returns:

  • (Object)


807
808
809
810
811
812
813
814
815
816
# File 'app/models/line_item.rb', line 807

def discounted_total
  # ALL itemizables: use line_discounts for exact totals (prevents rounding errors)
  # For example: -$50.00 / 3 qty = -$16.67 per unit, but -16.67 × 3 = -50.01 ❌
  # Using line_discounts ensures: price × qty + line_discounts = exact total ✓
  # This keeps Ruby calculations in sync with SQL calculate_itemizable_total function
  #
  # Use Ruby sum (map + sum) instead of ActiveRecord sum(:column) to leverage
  # preloaded line_discounts and avoid N+1 SUM queries during batch operations.
  price_total + line_discounts.sum(&:amount)
end

#discountsActiveRecord::Relation<Discount>

Discounts.

Returns:

See Also:



150
# File 'app/models/line_item.rb', line 150

has_many :discounts, through: :line_discounts

#discounts_totalObject

Discounts total.

Returns:

  • (Object)


828
829
830
# File 'app/models/line_item.rb', line 828

def discounts_total
  price_total - discounted_total
end

#dropship?Boolean

Whether dropship.

Returns:

  • (Boolean)


655
656
657
# File 'app/models/line_item.rb', line 655

def dropship?
  item.present? && item.dropship?
end

#effective_discountObject

Effective discount.

Returns:

  • (Object)


864
865
866
867
# File 'app/models/line_item.rb', line 864

def effective_discount
  d = price_total - discounted_total
  price_total.positive? && d.positive? ? ((d * 100) / price_total).to_i : 0
end

#estimated_line_costObject

Estimated line cost.

Returns:

  • (Object)


940
941
942
943
944
945
946
# File 'app/models/line_item.rb', line 940

def estimated_line_cost
  if is_shipping?
    price
  else
    current_line_cogs
  end
end

#fulfillment_origin_addressObject

Fulfillment origin address.

Returns:

  • (Object)


1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
# File 'app/models/line_item.rb', line 1030

def fulfillment_origin_address
  # TBD we need to properly populate this at store_item level, for drop shipping from a supplier with multiple addresses, e.g. HeatLay or HeatTrack  or WY office vs WY warehouse, etc.
  if is_service?
    if is_remote_service?
      res = resource.store.warehouse_service_address
    else
      res = delivery&.destination_address
      res ||= resource&.shipping_address
    end
  elsif is_shipping?
    res = delivery.origin_address if delivery
    res ||= resource.store.warehouse_address
    # logger.info "line item, fulfillment_origin_address: #{self.name}, shipping line, fulfillment_origin_address.id: #{res.id}"
  else
    res = store_item&.store&.warehouse_address
  end
  if item.dropship
    res = item.supplier_item.supplier.shipping_address
    # puts "line item: #{self.name}, dropship, fulfillment_origin_address.id: #{res.id}"
  end
  # puts "line item: #{self.name}, fulfillment_origin_address.id: #{res.id rescue NONE}"
  res
end

#fulfillment_origin_address_idObject

Fulfillment origin address id.

Returns:

  • (Object)


1024
1025
1026
# File 'app/models/line_item.rb', line 1024

def fulfillment_origin_address_id
  fulfillment_origin_address&.id
end

#get_item_idObject

Get item id.

Returns:

  • (Object)


649
650
651
# File 'app/models/line_item.rb', line 649

def get_item_id
  item&.id
end

#handling_chargeObject

Handling charge.

Returns:

  • (Object)


992
993
994
# File 'app/models/line_item.rb', line 992

def handling_charge
  catalog_item&.store_item&.handling_charge
end

#has_children?Boolean

Whether has children.

Returns:

  • (Boolean)


768
769
770
# File 'app/models/line_item.rb', line 768

def has_children?
  children_count.present? && (children_count > 0)
end

#has_linked_unvoided_rma_item?Boolean

Whether has linked unvoided rma item.

Returns:

  • (Boolean)


553
554
555
# File 'app/models/line_item.rb', line 553

def has_linked_unvoided_rma_item?
  credit_rma_item.present? && !credit_rma_item.voided?
end

#image_pathObject

Alias for Item#image_path

Returns:

  • (Object)

    Item#image_path

See Also:



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'app/models/line_item.rb', line 265

delegate :is_oj_programmable_tstat?,
:is_smartstat?,
:is_cork?,
:is_relay_panel?,
:is_legacy_relay?,
:is_publication?,
:maximum_current,
:num_poles,
:category_name,
:image_path,
:image_url,
:thumbnail_url,
:is_floor_heating_product?,
:oversize?,
:ships_via_freight?,
:product_tax_code,
:is_snow_melting_product?,
:is_reviewable?,
:product_line_for_review,
to: :item,
allow_nil: true

#image_urlObject

Alias for Item#image_url

Returns:

  • (Object)

    Item#image_url

See Also:



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'app/models/line_item.rb', line 265

delegate :is_oj_programmable_tstat?,
:is_smartstat?,
:is_cork?,
:is_relay_panel?,
:is_legacy_relay?,
:is_publication?,
:maximum_current,
:num_poles,
:category_name,
:image_path,
:image_url,
:thumbnail_url,
:is_floor_heating_product?,
:oversize?,
:ships_via_freight?,
:product_tax_code,
:is_snow_melting_product?,
:is_reviewable?,
:product_line_for_review,
to: :item,
allow_nil: true

#in_stock?Boolean

Whether in stock.

Returns:

  • (Boolean)


696
697
698
699
700
701
702
# File 'app/models/line_item.rb', line 696

def in_stock?
  if catalog_item # coupons don't have the same data as line items
    catalog_item.qty_available >= quantity
  else
    true
  end
end

#inventory_commitsActiveRecord::Relation<InventoryCommit>

Inventory commits.

Returns:

See Also:



152
# File 'app/models/line_item.rb', line 152

has_many :inventory_commits, dependent: :destroy

#invoiceInvoice

Invoice.

Returns:

See Also:



109
# File 'app/models/line_item.rb', line 109

belongs_to :invoice, -> { joins(:line_items).where(line_items: { resource_type: 'Invoice' }) }, foreign_key: :resource_id, optional: true

#is_cork?Object

Alias for Item#is_cork?

Returns:

  • (Object)

    Item#is_cork?

See Also:



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'app/models/line_item.rb', line 265

delegate :is_oj_programmable_tstat?,
:is_smartstat?,
:is_cork?,
:is_relay_panel?,
:is_legacy_relay?,
:is_publication?,
:maximum_current,
:num_poles,
:category_name,
:image_path,
:image_url,
:thumbnail_url,
:is_floor_heating_product?,
:oversize?,
:ships_via_freight?,
:product_tax_code,
:is_snow_melting_product?,
:is_reviewable?,
:product_line_for_review,
to: :item,
allow_nil: true

#is_discounted?Boolean

Whether is discounted.

Returns:

  • (Boolean)


871
872
873
# File 'app/models/line_item.rb', line 871

def is_discounted?
  effective_discount > 0
end

#is_floor_heating_product?Object

Alias for Item#is_floor_heating_product?

Returns:

  • (Object)

    Item#is_floor_heating_product?

See Also:



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'app/models/line_item.rb', line 265

delegate :is_oj_programmable_tstat?,
:is_smartstat?,
:is_cork?,
:is_relay_panel?,
:is_legacy_relay?,
:is_publication?,
:maximum_current,
:num_poles,
:category_name,
:image_path,
:image_url,
:thumbnail_url,
:is_floor_heating_product?,
:oversize?,
:ships_via_freight?,
:product_tax_code,
:is_snow_melting_product?,
:is_reviewable?,
:product_line_for_review,
to: :item,
allow_nil: true

#is_fulfillable?Boolean

Whether is fulfillable.

Returns:

  • (Boolean)


756
757
758
# File 'app/models/line_item.rb', line 756

def is_fulfillable?
  %i[ok low na].include?(stock_status)
end

#is_goods?Boolean

Whether is goods.

Returns:

  • (Boolean)


1078
1079
1080
# File 'app/models/line_item.rb', line 1078

def is_goods?
  tax_class.present? ? tax_class == 'g' : item&.is_goods?&.to_b
end

#is_kit?Boolean

Whether is kit.

Returns:

  • (Boolean)


539
540
541
# File 'app/models/line_item.rb', line 539

def is_kit?
  item&.is_kit?
end

#is_legacy_relay?Object

Alias for Item#is_legacy_relay?

Returns:

  • (Object)

    Item#is_legacy_relay?

See Also:



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'app/models/line_item.rb', line 265

delegate :is_oj_programmable_tstat?,
:is_smartstat?,
:is_cork?,
:is_relay_panel?,
:is_legacy_relay?,
:is_publication?,
:maximum_current,
:num_poles,
:category_name,
:image_path,
:image_url,
:thumbnail_url,
:is_floor_heating_product?,
:oversize?,
:ships_via_freight?,
:product_tax_code,
:is_snow_melting_product?,
:is_reviewable?,
:product_line_for_review,
to: :item,
allow_nil: true

#is_linked_to_same_resource_as_deliveryObject

Is linked to same resource as delivery.

Returns:

  • (Object)


531
532
533
534
535
# File 'app/models/line_item.rb', line 531

def is_linked_to_same_resource_as_delivery
  return unless delivery&.resource && resource&.persisted? && delivery&.resource&.persisted? && resource.id != delivery.resource.id

  errors.add(:resource_id, 'is not the same as Delivery resource')
end

#is_not_a_kitObject

Is not a kit.

Returns:

  • (Object)


545
546
547
548
549
# File 'app/models/line_item.rb', line 545

def is_not_a_kit
  return unless is_kit?

  errors.add(:catalog_item_id, "#{sku} is a kit. Kits are not permitted on store transfers, please add kit components instead")
end

#is_oj_programmable_tstat?Object

Alias for Item#is_oj_programmable_tstat?

Returns:

  • (Object)

    Item#is_oj_programmable_tstat?

See Also:



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'app/models/line_item.rb', line 265

delegate :is_oj_programmable_tstat?,
:is_smartstat?,
:is_cork?,
:is_relay_panel?,
:is_legacy_relay?,
:is_publication?,
:maximum_current,
:num_poles,
:category_name,
:image_path,
:image_url,
:thumbnail_url,
:is_floor_heating_product?,
:oversize?,
:ships_via_freight?,
:product_tax_code,
:is_snow_melting_product?,
:is_reviewable?,
:product_line_for_review,
to: :item,
allow_nil: true

#is_onsite_service?Boolean

Whether is onsite service.

Returns:

  • (Boolean)


1102
1103
1104
# File 'app/models/line_item.rb', line 1102

def is_onsite_service?
  is_service? && item.sku.include?('ONSITE')
end

#is_publication?Object

Alias for Item#is_publication?

Returns:

  • (Object)

    Item#is_publication?

See Also:



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'app/models/line_item.rb', line 265

delegate :is_oj_programmable_tstat?,
:is_smartstat?,
:is_cork?,
:is_relay_panel?,
:is_legacy_relay?,
:is_publication?,
:maximum_current,
:num_poles,
:category_name,
:image_path,
:image_url,
:thumbnail_url,
:is_floor_heating_product?,
:oversize?,
:ships_via_freight?,
:product_tax_code,
:is_snow_melting_product?,
:is_reviewable?,
:product_line_for_review,
to: :item,
allow_nil: true

Whether is related smart service.

Parameters:

  • sku (Object)

Returns:

  • (Boolean)


1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
# File 'app/models/line_item.rb', line 1133

def is_related_smart_service?(sku)
  res = false
  # check if our SKU is the corresponding _PER_SQFT or _FIXRATE to the input SKU
  if sku.index('_FIXRATE') && is_smartinstall_service? && item.sku.index('_PER_SQFT') && item.sku.split('_PER_SQFT').first == sku.split('_FIXRATE')
    res = true
  elsif sku.index('_PER_SQFT') && is_smartinstall_service? && item.sku.index('_FIXRATE') && item.sku.split('_FIXRATE').first == sku.split('_PER_SQFT')
    res = true
  end
  res
end

#is_relay_panel?Object

Alias for Item#is_relay_panel?

Returns:

  • (Object)

    Item#is_relay_panel?

See Also:



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'app/models/line_item.rb', line 265

delegate :is_oj_programmable_tstat?,
:is_smartstat?,
:is_cork?,
:is_relay_panel?,
:is_legacy_relay?,
:is_publication?,
:maximum_current,
:num_poles,
:category_name,
:image_path,
:image_url,
:thumbnail_url,
:is_floor_heating_product?,
:oversize?,
:ships_via_freight?,
:product_tax_code,
:is_snow_melting_product?,
:is_reviewable?,
:product_line_for_review,
to: :item,
allow_nil: true

#is_remote_service?Boolean

Whether is remote service.

Returns:

  • (Boolean)


1096
1097
1098
# File 'app/models/line_item.rb', line 1096

def is_remote_service?
  (is_service? && item.sku.include?('REMOTE')) || (is_service? && resource.line_items.any? { |a| a.sku.include?('REMOTE') })
end

#is_reviewable?Object

Alias for Item#is_reviewable?

Returns:

  • (Object)

    Item#is_reviewable?

See Also:



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'app/models/line_item.rb', line 265

delegate :is_oj_programmable_tstat?,
:is_smartstat?,
:is_cork?,
:is_relay_panel?,
:is_legacy_relay?,
:is_publication?,
:maximum_current,
:num_poles,
:category_name,
:image_path,
:image_url,
:thumbnail_url,
:is_floor_heating_product?,
:oversize?,
:ships_via_freight?,
:product_tax_code,
:is_snow_melting_product?,
:is_reviewable?,
:product_line_for_review,
to: :item,
allow_nil: true

#is_service?Boolean

Whether is service.

Returns:

  • (Boolean)


1090
1091
1092
# File 'app/models/line_item.rb', line 1090

def is_service?
  tax_class.present? ? tax_class == 'svc' : item&.is_service?&.to_b
end

#is_shipping?Boolean

Whether is shipping.

Returns:

  • (Boolean)


1114
1115
1116
# File 'app/models/line_item.rb', line 1114

def is_shipping?
  tax_class.present? ? tax_class == 'shp' : item&.is_shipping?&.to_b
end

#is_smart_service?Boolean

Whether is smart service.

Returns:

  • (Boolean)


1126
1127
1128
# File 'app/models/line_item.rb', line 1126

def is_smart_service?
  item&.pl_descendant_of_path?(LtreePaths::PL_SERVICES)
end

#is_smartinstall_service?Boolean

Whether is smartinstall service.

Returns:

  • (Boolean)


1120
1121
1122
# File 'app/models/line_item.rb', line 1120

def is_smartinstall_service?
  item&.pl_descendant_of_path?(LtreePaths::PL_SERVICES_SMARTINSTALL)
end

#is_smartstat?Object

Alias for Item#is_smartstat?

Returns:

  • (Object)

    Item#is_smartstat?

See Also:



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'app/models/line_item.rb', line 265

delegate :is_oj_programmable_tstat?,
:is_smartstat?,
:is_cork?,
:is_relay_panel?,
:is_legacy_relay?,
:is_publication?,
:maximum_current,
:num_poles,
:category_name,
:image_path,
:image_url,
:thumbnail_url,
:is_floor_heating_product?,
:oversize?,
:ships_via_freight?,
:product_tax_code,
:is_snow_melting_product?,
:is_reviewable?,
:product_line_for_review,
to: :item,
allow_nil: true

#is_snow_melting_product?Object

Alias for Item#is_snow_melting_product?

Returns:

  • (Object)

    Item#is_snow_melting_product?

See Also:



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'app/models/line_item.rb', line 265

delegate :is_oj_programmable_tstat?,
:is_smartstat?,
:is_cork?,
:is_relay_panel?,
:is_legacy_relay?,
:is_publication?,
:maximum_current,
:num_poles,
:category_name,
:image_path,
:image_url,
:thumbnail_url,
:is_floor_heating_product?,
:oversize?,
:ships_via_freight?,
:product_tax_code,
:is_snow_melting_product?,
:is_reviewable?,
:product_line_for_review,
to: :item,
allow_nil: true

#is_spare_parts?Boolean

Whether is spare parts.

Returns:

  • (Boolean)


1084
1085
1086
# File 'app/models/line_item.rb', line 1084

def is_spare_parts?
  item&.is_spare_parts?&.to_b
end

#is_tax_unclassed?Boolean

Whether is tax unclassed.

Returns:

  • (Boolean)


1108
1109
1110
# File 'app/models/line_item.rb', line 1108

def is_tax_unclassed?
  tax_class == 'none'
end

#itemItem

Item.

Returns:

See Also:



128
# File 'app/models/line_item.rb', line 128

belongs_to :item, optional: true

#item_in_stock_in_storeObject (protected)

Item in stock in store.

Returns:

  • (Object)


1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
# File 'app/models/line_item.rb', line 1176

def item_in_stock_in_store
  return unless get_item_id && resource&.store_id

  store_item = StoreItem.available.where(item_id: get_item_id, store_id: resource.store_id).first
  if store_item.nil?
    errors.add(:item_id, 'is not available in selected store')
  elsif quantity && (store_item.qty_available < quantity)
    errors.add(:quantity, 'is more than quantity available in selected store')
  end
end

#kit_component?Boolean

A kit-component child line: it lives under an expanded kit parent (the
expand_kit DB trigger inserts these at price 0) and its price is owned by
the parent, not editable on its own. On line items, lineage is only ever
kit expansion, so a present parent_id is the invariant. See
#enforce_zero_price_on_kit_component for the save-time enforcement.

Returns:

  • (Boolean)


779
780
781
# File 'app/models/line_item.rb', line 779

def kit_component?
  parent_id.present?
end

#ledger_company_accountLedgerCompanyAccount

Ledger company account.



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

belongs_to :ledger_company_account, optional: true

#ledger_descriptionObject

Ledger description.

Returns:

  • (Object)


639
640
641
642
643
644
645
# File 'app/models/line_item.rb', line 639

def ledger_description
  if cm_category.nil? || ((cm_category == 'Item') && sku.present?)
    sku
  else
    "#{cm_category} #{description}"
  end
end

#line_discountsActiveRecord::Relation<LineDiscount>

Line discounts.

Returns:

See Also:



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

has_many :line_discounts, dependent: :destroy, inverse_of: :line_item, autosave: true

#line_locked?Boolean

Whether line locked.

Returns:

  • (Boolean)


661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
# File 'app/models/line_item.rb', line 661

def line_locked?
  return true if resource.respond_to?(:editing_locked?) && resource.editing_locked?
  # If an item is not plan sensitive, we don't care
  return false unless item.plan_sensitive?

  # If line item is linked to a room within a resource, we do a sanity check to ensure that
  # room is also part of the resource's room configurations
  return false unless room_configuration
  # If the line item is linked to a room not in the resource, don't lock it and do as you please
  return false if resource.respond_to?(:room_configuration_ids) && resource.room_configuration_ids.exclude?(room_configuration.id)

  # Otherwise the room dictates the locking status of that item
  room_configuration.editing_locked?

  # Not relevant edit as you wish
end

#line_total_with_taxObject

Line total with tax.

Returns:

  • (Object)


820
821
822
823
824
# File 'app/models/line_item.rb', line 820

def line_total_with_tax
  discounted_total + (tax_total || 0.0)
rescue StandardError
  0
end

#linear_ft_totalObject

Linear ft total.

Returns:

  • (Object)


858
859
860
# File 'app/models/line_item.rb', line 858

def linear_ft_total
  quantity * (item.length.to_f / 12)
end

#linked_to_credit_memoObject

Linked to credit memo.

Returns:

  • (Object)


573
574
575
# File 'app/models/line_item.rb', line 573

def linked_to_credit_memo
  resource_type == 'CreditMemo'
end

#linked_to_invoiceObject

Linked to invoice.

Returns:

  • (Object)


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

def linked_to_invoice
  resource_type == 'Invoice'
end

#linked_to_st_orderObject

Linked to st order.

Returns:

  • (Object)


615
616
617
# File 'app/models/line_item.rb', line 615

def linked_to_st_order
  (resource_type == 'Order') && (resource.try(:order_type) == 'ST')
end

#linked_to_standalone_credit_memoObject

Linked to standalone credit memo.

Returns:

  • (Object)


597
598
599
# File 'app/models/line_item.rb', line 597

def linked_to_standalone_credit_memo
  (resource_type == 'CreditMemo') && (resource.try(:category) == 'standalone')
end

#linked_to_standalone_credit_memo_or_invoiceObject

Linked to standalone credit memo or invoice.

Returns:

  • (Object)


609
610
611
# File 'app/models/line_item.rb', line 609

def linked_to_standalone_credit_memo_or_invoice
  linked_to_standalone_credit_memo || linked_to_standalone_invoice
end

#linked_to_standalone_invoiceObject

Linked to standalone invoice.

Returns:

  • (Object)


603
604
605
# File 'app/models/line_item.rb', line 603

def linked_to_standalone_invoice
  (resource_type == 'Invoice') && [Invoice::MI].include?(resource.try(:invoice_type))
end

#max_discountObject

Determine the max discount in effect for this line item
Only for quote or order



413
414
415
416
417
418
419
420
# File 'app/models/line_item.rb', line 413

def max_discount
  md = nil
  if (discountable_resource = quote || order)
    md = discountable_resource.max_discount_override
  end
  md = catalog_item.max_discount if md.nil? && catalog_item
  md
end

#maximum_currentObject

Alias for Item#maximum_current

Returns:

  • (Object)

    Item#maximum_current

See Also:



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'app/models/line_item.rb', line 265

delegate :is_oj_programmable_tstat?,
:is_smartstat?,
:is_cork?,
:is_relay_panel?,
:is_legacy_relay?,
:is_publication?,
:maximum_current,
:num_poles,
:category_name,
:image_path,
:image_url,
:thumbnail_url,
:is_floor_heating_product?,
:oversize?,
:ships_via_freight?,
:product_tax_code,
:is_snow_melting_product?,
:is_reviewable?,
:product_line_for_review,
to: :item,
allow_nil: true

#msrpObject

Alias for Catalog_item#msrp

Returns:

  • (Object)

    Catalog_item#msrp

See Also:



287
288
289
290
291
292
293
# File 'app/models/line_item.rb', line 287

delegate :price_with_vat,
:sale_price_with_vat,
:msrp,
:msrp_with_vat,
:root_catalog_item,
to: :catalog_item,
allow_nil: true

#msrp_totalObject

Msrp total.

Returns:

  • (Object)


842
843
844
845
846
847
848
# File 'app/models/line_item.rb', line 842

def msrp_total
  return 0.0 if parent_id.present?

  (msrp || 0.0) * quantity
rescue StandardError
  0.0
end

#msrp_with_vatObject

Alias for Catalog_item#msrp_with_vat

Returns:

  • (Object)

    Catalog_item#msrp_with_vat

See Also:



287
288
289
290
291
292
293
# File 'app/models/line_item.rb', line 287

delegate :price_with_vat,
:sale_price_with_vat,
:msrp,
:msrp_with_vat,
:root_catalog_item,
to: :catalog_item,
allow_nil: true

#nameObject

Name.

Returns:

  • (Object)


633
634
635
# File 'app/models/line_item.rb', line 633

def name
  description || item.try(:name) || item_name
end

#num_polesObject

Alias for Item#num_poles

Returns:

  • (Object)

    Item#num_poles

See Also:



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'app/models/line_item.rb', line 265

delegate :is_oj_programmable_tstat?,
:is_smartstat?,
:is_cork?,
:is_relay_panel?,
:is_legacy_relay?,
:is_publication?,
:maximum_current,
:num_poles,
:category_name,
:image_path,
:image_url,
:thumbnail_url,
:is_floor_heating_product?,
:oversize?,
:ships_via_freight?,
:product_tax_code,
:is_snow_melting_product?,
:is_reviewable?,
:product_line_for_review,
to: :item,
allow_nil: true

#orderOrder

The following makes join easier

Returns:

See Also:



105
# File 'app/models/line_item.rb', line 105

belongs_to :order, -> { joins(:line_items).where(line_items: { resource_type: 'Order' }) }, foreign_key: :resource_id, optional: true

#oversize?Object

Alias for Item#oversize?

Returns:

  • (Object)

    Item#oversize?

See Also:



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'app/models/line_item.rb', line 265

delegate :is_oj_programmable_tstat?,
:is_smartstat?,
:is_cork?,
:is_relay_panel?,
:is_legacy_relay?,
:is_publication?,
:maximum_current,
:num_poles,
:category_name,
:image_path,
:image_url,
:thumbnail_url,
:is_floor_heating_product?,
:oversize?,
:ships_via_freight?,
:product_tax_code,
:is_snow_melting_product?,
:is_reviewable?,
:product_line_for_review,
to: :item,
allow_nil: true

#parent_quantity_factorObject

Determines the parent line quantity



423
424
425
426
427
428
429
430
431
432
433
434
# File 'app/models/line_item.rb', line 423

def parent_quantity_factor
  return unless parent&.quantity

  # what type of item is the parent
  # parent_item = parent.item
  # how many of this component item are normally in the parent item
  # tir = parent_item.kit_target_item_relations.where(target_item_id: item_id).first
  # component_quantity = tir.quantity
  (quantity / parent.quantity).to_i
  # How many quantity in this line item
  # component_quantity
end

#preset_jobPresetJob

Preset job.

Returns:

See Also:



141
# File 'app/models/line_item.rb', line 141

has_one :preset_job, dependent: :destroy

#price_editable?(account = nil) ⇒ Boolean

Whether price editable.

Parameters:

  • account (Object, nil) (defaults to: nil)

Returns:

  • (Boolean)


1006
1007
1008
1009
1010
1011
1012
# File 'app/models/line_item.rb', line 1006

def price_editable?( = nil)
  .has_role?('sales_manager') || .has_role?('admin') || (begin
    catalog_item.price_editable
  rescue StandardError
    false
  end)
end

#price_totalObject

Price total.

Returns:

  • (Object)


834
835
836
837
838
# File 'app/models/line_item.rb', line 834

def price_total
  (price || 0.0) * quantity
rescue StandardError
  0.0
end

#price_with_vatObject

Alias for Catalog_item#price_with_vat

Returns:

  • (Object)

    Catalog_item#price_with_vat

See Also:



287
288
289
290
291
292
293
# File 'app/models/line_item.rb', line 287

delegate :price_with_vat,
:sale_price_with_vat,
:msrp,
:msrp_with_vat,
:root_catalog_item,
to: :catalog_item,
allow_nil: true

#product_line_for_reviewObject

Alias for Item#product_line_for_review

Returns:

  • (Object)

    Item#product_line_for_review

See Also:



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'app/models/line_item.rb', line 265

delegate :is_oj_programmable_tstat?,
:is_smartstat?,
:is_cork?,
:is_relay_panel?,
:is_legacy_relay?,
:is_publication?,
:maximum_current,
:num_poles,
:category_name,
:image_path,
:image_url,
:thumbnail_url,
:is_floor_heating_product?,
:oversize?,
:ships_via_freight?,
:product_tax_code,
:is_snow_melting_product?,
:is_reviewable?,
:product_line_for_review,
to: :item,
allow_nil: true

#product_tax_codeObject

Alias for Item#product_tax_code

Returns:

  • (Object)

    Item#product_tax_code

See Also:



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'app/models/line_item.rb', line 265

delegate :is_oj_programmable_tstat?,
:is_smartstat?,
:is_cork?,
:is_relay_panel?,
:is_legacy_relay?,
:is_publication?,
:maximum_current,
:num_poles,
:category_name,
:image_path,
:image_url,
:thumbnail_url,
:is_floor_heating_product?,
:oversize?,
:ships_via_freight?,
:product_tax_code,
:is_snow_melting_product?,
:is_reviewable?,
:product_line_for_review,
to: :item,
allow_nil: true

#purchase_order_itemPurchaseOrderItem

Purchase order item.



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

has_one :purchase_order_item, dependent: :restrict_with_error

#quantities_indicator_visible?Boolean

Whether quantities indicator visible.

Returns:

  • (Boolean)


1016
1017
1018
1019
1020
# File 'app/models/line_item.rb', line 1016

def quantities_indicator_visible?
  item.quantities_indicator_visible?
rescue StandardError
  true
end

#quoteQuote

Quote.

Returns:

See Also:



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

belongs_to :quote, -> { joins(:line_items).where(line_items: { resource_type: 'Quote' }) }, foreign_key: :resource_id, optional: true

#remaining_quantity_to_allocate_to_shipmentsObject

Remaining quantity to allocate to shipments.

Returns:

  • (Object)


515
516
517
# File 'app/models/line_item.rb', line 515

def remaining_quantity_to_allocate_to_shipments
  quantity - shipment_contents.sum(:quantity)
end

#replacement_rma_itemRmaItem

Replacement rma item.

Returns:

See Also:



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

belongs_to :replacement_rma_item, class_name: 'RmaItem', optional: true

#reported_category_nameObject

Reported category name.

Returns:

  • (Object)


1152
1153
1154
# File 'app/models/line_item.rb', line 1152

def reported_category_name
  item&.primary_product_line&.lineage_expanded || 'unknown'
end

#require_serial_number?Boolean

Whether require serial number.

Returns:

  • (Boolean)


998
999
1000
1001
# File 'app/models/line_item.rb', line 998

def require_serial_number?
  false
  # catalog_item.store_item.requires_serial_number? rescue false
end

#resourceResource

Resource.

Returns:

  • (Resource)

See Also:



103
# File 'app/models/line_item.rb', line 103

belongs_to :resource, polymorphic: true, inverse_of: :line_items, optional: true

#reviewReview

Review.

Returns:

See Also:



143
# File 'app/models/line_item.rb', line 143

has_one :review, dependent: :restrict_with_error

#rma_itemsActiveRecord::Relation<RmaItem>

Rma items.

Returns:

  • (ActiveRecord::Relation<RmaItem>)

See Also:



146
# File 'app/models/line_item.rb', line 146

has_many :rma_items, foreign_key: 'returned_line_item_id', dependent: :destroy, inverse_of: :returned_line_item

#room_configurationRoomConfiguration

Room configuration.



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

belongs_to :room_configuration, optional: true

#root_catalog_itemObject

Alias for Catalog_item#root_catalog_item

Returns:

  • (Object)

    Catalog_item#root_catalog_item

See Also:



287
288
289
290
291
292
293
# File 'app/models/line_item.rb', line 287

delegate :price_with_vat,
:sale_price_with_vat,
:msrp,
:msrp_with_vat,
:root_catalog_item,
to: :catalog_item,
allow_nil: true

#sale_price_with_vatObject

Alias for Catalog_item#sale_price_with_vat

Returns:

  • (Object)

    Catalog_item#sale_price_with_vat

See Also:



287
288
289
290
291
292
293
# File 'app/models/line_item.rb', line 287

delegate :price_with_vat,
:sale_price_with_vat,
:msrp,
:msrp_with_vat,
:root_catalog_item,
to: :catalog_item,
allow_nil: true

#serial_numberSerialNumber

Serial number.



118
# File 'app/models/line_item.rb', line 118

belongs_to :serial_number, optional: true

#set_cogsObject (protected)

Set cogs.

Returns:

  • (Object)


1189
1190
1191
1192
1193
1194
1195
1196
1197
# File 'app/models/line_item.rb', line 1189

def set_cogs
  return unless !quantity.nil? && unit_cogs.nil? && resource.store_id.present?

  store_item = StoreItem.available.where(item_id: get_item_id, store_id: resource.store_id).first
  return if store_item.nil?

  self.unit_cogs = store_item.unit_cogs
  self.total_cogs = store_item.unit_cogs * quantity
end

#set_cogs_for_store_transferObject (protected)

Set cogs for store transfer.

Returns:

  • (Object)


1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
# File 'app/models/line_item.rb', line 1201

def set_cogs_for_store_transfer
  return unless resource.try(:is_store_transfer?)
  return if unit_cogs.present? || quantity.nil?

  store_item = self.store_item
  return if store_item.nil?

  self.unit_cogs = store_item.unit_cogs
  self.total_cogs = store_item.unit_cogs * quantity if store_item.unit_cogs.present?
end

#shipment_contentsActiveRecord::Relation<ShipmentContent>

Shipment contents.

Returns:

See Also:



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

has_many :shipment_contents, dependent: :destroy

#shipping_costShippingCost

Shipping cost.



134
# File 'app/models/line_item.rb', line 134

belongs_to :shipping_cost, optional: true

#shipping_nameObject

Shipping name.

Returns:

  • (Object)


1146
1147
1148
# File 'app/models/line_item.rb', line 1146

def shipping_name
  delivery&.retrieve_shipping_description_for_shipping_cost(shipping_cost) || name
end

#shipping_weightObject

Shipping weight.

Returns:

  • (Object)


979
980
981
982
# File 'app/models/line_item.rb', line 979

def shipping_weight
  # important: item.base_weight is the better quality number and means Net Unit Weight so the correct number to use when calculating item weight because tare weight (of the box, pallet, crate etc) is added after
  item&.base_weight || 0.0
end

#ships_via_freight?Object

Alias for Item#ships_via_freight?

Returns:

  • (Object)

    Item#ships_via_freight?

See Also:



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'app/models/line_item.rb', line 265

delegate :is_oj_programmable_tstat?,
:is_smartstat?,
:is_cork?,
:is_relay_panel?,
:is_legacy_relay?,
:is_publication?,
:maximum_current,
:num_poles,
:category_name,
:image_path,
:image_url,
:thumbnail_url,
:is_floor_heating_product?,
:oversize?,
:ships_via_freight?,
:product_tax_code,
:is_snow_melting_product?,
:is_reviewable?,
:product_line_for_review,
to: :item,
allow_nil: true

#show_out_of_stock?(item_availability = {}) ⇒ Boolean

Whether show out of stock.

Parameters:

  • item_availability (Hash) (defaults to: {})

Returns:

  • (Boolean)


707
708
709
710
711
712
713
714
715
716
717
# File 'app/models/line_item.rb', line 707

def show_out_of_stock?(item_availability = {})
  if dropship? && resource.respond_to?(:single_origin?) && resource.single_origin?
    !in_stock?
  elsif dropship? || item.try(:always_available_online?)
    false
  elsif !item_availability.empty? && item_availability[catalog_item_id].present?
    !item_availability[catalog_item_id]['in_stock']
  else
    !in_stock?
  end
end

#single_origin_or_warehouse_pickup?Boolean

Whether single origin or warehouse pickup.

Returns:

  • (Boolean)


721
722
723
724
725
# File 'app/models/line_item.rb', line 721

def single_origin_or_warehouse_pickup?
  return false if resource.is_a?(RoomConfiguration)

  resource.single_origin? || resource.is_warehouse_pickup?
end

#skuObject

Sku.

Returns:

  • (Object)


621
622
623
# File 'app/models/line_item.rb', line 621

def sku
  item&.sku || sku_override
end

#sort_priorityObject

Sort priority.

Returns:

  • (Object)


449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
# File 'app/models/line_item.rb', line 449

def sort_priority
  i = item || catalog_item&.store_item&.item
  return [0, 0, 0, 0] unless i

  s1 = i.product_category_priority
  if i.is_heating_element?
    s2 = -begin
      i.width.to_i
    rescue StandardError
      0
    end
    s3 = -begin
      i.length.to_i
    rescue StandardError
      0
    end
  else
    s2 = 0
    s3 = 0
  end
  s4 = i.sku
  [s1, s2, s3, s4]
end

#sq_ft_totalObject

Sq ft total.

Returns:

  • (Object)


852
853
854
# File 'app/models/line_item.rb', line 852

def sq_ft_total
  quantity * item.sqft.to_f
end

#stock_statusObject

Stock status.

Returns:

  • (Object)


729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
# File 'app/models/line_item.rb', line 729

def stock_status
  return :none unless catalog_item
  return :na if quantity < 0 # not applicable for credit orders
  return :ok if dropship? && !single_origin_or_warehouse_pickup?
  return :ok if unlimited_inventory?

  # Check if this specific line item has enough commits to fulfill its quantity.
  # Use Enumerable#sum (block form) so preloaded inventory_commits are used in-memory
  # without firing a SQL SUM aggregate per line item.
  committed_quantity = inventory_commits.sum(&:quantity)
  return :ok if committed_quantity >= quantity

  si = resource.respond_to?(:from_store_id) && resource.from_store_id ? StoreItem.where(store_id: resource.from_store_id, item_id:, location: 'AVAILABLE').first : store_item
  return :oos if si.qty_available < quantity
  return :low if item.qty_warn_on_stock && (si.qty_available <= item.qty_warn_on_stock)

  :ok
end

#store_itemObject

Store item.

Returns:

  • (Object)


762
763
764
# File 'app/models/line_item.rb', line 762

def store_item
  direct_store_item || catalog_item&.store_item
end

#supplier_skusObject

Supplier skus.

Returns:

  • (Object)


627
628
629
# File 'app/models/line_item.rb', line 627

def supplier_skus
  item&.supplier_skus || []
end

#taxable_totalObject

Taxable total.

Returns:

  • (Object)


877
878
879
880
881
882
883
# File 'app/models/line_item.rb', line 877

def taxable_total
  if invoice&.invoice_type == Invoice::CI
    discounted_total
  else
    taxable_amount.present? ? (taxable_amount * quantity) : discounted_total
  end
end

#taxes_grouped_by_typeObject

Taxes grouped by type.

Returns:

  • (Object)


438
439
440
441
442
443
444
445
# File 'app/models/line_item.rb', line 438

def taxes_grouped_by_type
  tax_grouped = {}
  if tax_rate.present? && tax_type.present?
    rate_percentage = (tax_rate * 100)
    tax_grouped[tax_type] = { tax_amount: tax_total, name: "#{TaxRate.description_for(tax_type)} #{rate_percentage}%", rate: rate_percentage }
  end
  tax_grouped
end

#thumbnail_urlObject

Alias for Item#thumbnail_url

Returns:

  • (Object)

    Item#thumbnail_url

See Also:



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'app/models/line_item.rb', line 265

delegate :is_oj_programmable_tstat?,
:is_smartstat?,
:is_cork?,
:is_relay_panel?,
:is_legacy_relay?,
:is_publication?,
:maximum_current,
:num_poles,
:category_name,
:image_path,
:image_url,
:thumbnail_url,
:is_floor_heating_product?,
:oversize?,
:ships_via_freight?,
:product_tax_code,
:is_snow_melting_product?,
:is_reviewable?,
:product_line_for_review,
to: :item,
allow_nil: true

#totalObject

Total.

Returns:

  • (Object)


887
888
889
# File 'app/models/line_item.rb', line 887

def total
  discounted_total
end

#total_shipping_weightObject

Total shipping weight.

Returns:

  • (Object)


986
987
988
# File 'app/models/line_item.rb', line 986

def total_shipping_weight
  (shipping_weight * quantity)
end

#unit_supplier_purchase_costObject

def total=(value)
end



893
894
895
# File 'app/models/line_item.rb', line 893

def unit_supplier_purchase_cost
  item.try(:supplier_items)&.last&.current_price&.purchasing_cost
end

#unit_value_for_commercial_invoiceObject

Unit value for commercial invoice.

Returns:

  • (Object)


899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
# File 'app/models/line_item.rb', line 899

def unit_value_for_commercial_invoice
  is_intra_company_or_electronic_ci_st = delivery&.order&.is_store_transfer? && (delivery&.order&.shipping_address&.is_warehouse_by_distance? || delivery&.electronic_ship_ci_pdf.present? || delivery&.should_have_electronic_commercial_invoice?) # test that we should use minimized customs value when not visible to end customer, that is, an intra-company ST or an ST shipped with electronic CI
  min_unit_value_arr = []
  min_unit_value_arr << discounted_price if discounted_price&.positive? # skip $0 here
  min_unit_value_arr << unit_supplier_purchase_cost if unit_supplier_purchase_cost&.positive? # skip $0 here
  min_unit_value_arr << unit_cogs if unit_cogs&.positive? # skip $0 here
  min_unit_value = min_unit_value_arr.min # now minimun will choose minimum of these three non-zero unit costs
  effective_price = discounted_price
  effective_price = unit_cogs unless effective_price&.positive? # have a fallback
  effective_price = unit_supplier_purchase_cost unless effective_price&.positive? # have a fallback
  effective_price = price unless effective_price&.positive? # have a fallback
  # For intra-company store transfers, use min_unit_value if available, otherwise fall back to effective_price
  # This handles cases where all three values (discounted_price, unit_supplier_purchase_cost, unit_cogs) are nil or not positive
  (is_intra_company_or_electronic_ci_st ? (min_unit_value || effective_price) : effective_price)
end

#unlimited_inventory?Boolean

Whether unlimited inventory.

Returns:

  • (Boolean)


750
751
752
# File 'app/models/line_item.rb', line 750

def unlimited_inventory?
  store_item&.unlimited_inventory?
end

#validate_quantity_limitObject

Validate quantity limit.

Returns:

  • (Object)


521
522
523
524
525
526
527
# File 'app/models/line_item.rb', line 521

def validate_quantity_limit
  return unless quantity && (ql = store_item&.quantity_limit)

  return unless quantity > ql

  errors.add(:quantity, "quantity limit #{ql} exceeded for this item")
end

#voidObject

Void.

Returns:

  • (Object)


1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
# File 'app/models/line_item.rb', line 1056

def void
  puts 'line_item.void'
  if resource.line_items.goods.length == 1
    # here we do an RMA cancel since this is the only time that this is called.
    resource.rma_cancel
  else
    order = resource
    destroy
    # try to mark the credit order as returned
    order.reload
    order.returned
  end
end