Module: MyCartHelper

Defined in:
app/helpers/my_cart_helper.rb

Overview

View helper: my cart.

Instance Method Summary collapse

Instance Method Details

#billing_zip_code_for_ccObject



172
173
174
175
176
# File 'app/helpers/my_cart_helper.rb', line 172

def billing_zip_code_for_cc
  @cart.billing_address.zip
rescue StandardError
  nil
end

#calculate_tier_adjusted_total(itemizable) ⇒ Object

Calculate tier-adjusted total for an itemizable



284
285
286
287
288
289
# File 'app/helpers/my_cart_helper.rb', line 284

def calculate_tier_adjusted_total(itemizable)
  tier_discount = calculate_tier_discount_total(itemizable)
  return nil unless tier_discount

  (itemizable.total - tier_discount).round(2)
end

#calculate_tier_discount_total(itemizable) ⇒ Object

Calculate total tier discount for an itemizable (Quote/Order)



254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'app/helpers/my_cart_helper.rb', line 254

def calculate_tier_discount_total(itemizable)
  return nil unless @context_user.respond_to?(:pricing_program_discount)

  tier_discount_pct = @context_user.pricing_program_discount.to_f
  return nil if tier_discount_pct.zero?

  total_discount = 0.0
  itemizable.line_items.non_shipping.each do |li|
    original_price = li.price.to_f
    next if original_price.zero?

    max_discount = li.catalog_item&.max_discount || 100
    effective_discount_pct = [tier_discount_pct, max_discount].min
    effective_discount_factor = effective_discount_pct / 100.0

    tier_price = (original_price * (1 - effective_discount_factor)).round(2)

    # Check if sale price is better
    sale_price = li.catalog_item&.sale_price_in_effect? ? li.catalog_item.sale_price.to_f : nil
    next if sale_price.present? && sale_price < tier_price

    # Add the discount for this item (original - tier) * quantity
    discount_per_unit = original_price - tier_price
    total_discount += discount_per_unit * li.quantity
  end

  total_discount.positive? ? total_discount.round(2) : nil
end

#calculate_tier_price_for_line_item(li) ⇒ Object

Calculate tier price for a line item if the current user has tier pricing



228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'app/helpers/my_cart_helper.rb', line 228

def calculate_tier_price_for_line_item(li)
  return nil unless @context_user.respond_to?(:pricing_program_discount)

  tier_discount_pct = @context_user.pricing_program_discount.to_f
  return nil if tier_discount_pct.zero?

  original_price = li.price.to_f
  return nil if original_price.zero?

  # Get max discount cap for this item
  max_discount = li.catalog_item&.max_discount || 100
  effective_discount_pct = [tier_discount_pct, max_discount].min
  effective_discount_factor = effective_discount_pct / 100.0

  tier_price = (original_price * (1 - effective_discount_factor)).round(2)

  # Check if sale price is better
  sale_price = li.catalog_item&.sale_price_in_effect? ? li.catalog_item.sale_price.to_f : nil
  if sale_price.present? && sale_price < tier_price
    return nil # Sale price is better, don't show tier pricing
  end

  tier_price
end

#cart_counter(_cart = nil) ⇒ Object



437
438
439
# File 'app/helpers/my_cart_helper.rb', line 437

def cart_counter(_cart = nil)
  @context_user&.cart_info.try(:[], :quantities) || 0
end

#cart_sale_price_savings(cart) ⇒ Object

Calculate total savings from sale prices and tier pricing (used in "You're saving" display)



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

def cart_sale_price_savings(cart)
  savings = 0.0
  cart.line_items.active_non_shipping_lines.each do |li|
    if li.is_discounted?
      savings += li.price_total - li.discounted_total
    elsif li.catalog_item&.sale_price_in_effect?
      savings += li.price_total - (li.catalog_item.sale_price * li.quantity)
    elsif (tier_price = calculate_tier_price_for_line_item(li))
      savings += li.price_total - (tier_price * li.quantity)
    end
  end
  savings
end

#checkout_breadcrumb(show_billing, show_delivery, show_payment) ⇒ Object



441
442
443
444
445
446
447
448
# File 'app/helpers/my_cart_helper.rb', line 441

def checkout_breadcrumb(show_billing, show_delivery, show_payment)
  links = [{ name: 'Shopping Cart', url: cms_link('/my_cart') }]
  links << { name: 'Billing Details', url: show_billing ? cms_link('/my_cart/customer_info') : nil }
  links << { name: 'Delivery Options', url: show_delivery ? cms_link('/my_cart/shipping') : nil }
  links << { name: 'Payment', url: show_payment ? cms_link('/my_cart/payment') : nil }
  # Standardize on formatted_breadcrumb for consistent rendering across www
  content_for(:formatted_breadcrumb, formatted_breadcrumb(links))
end

#display_discounted_line_price_for_catalog_item(ci, qty, target) ⇒ Object



380
381
382
383
384
385
386
387
388
# File 'app/helpers/my_cart_helper.rb', line 380

def display_discounted_line_price_for_catalog_item(ci, qty, target)
  currency_symbol = begin
    target.currency_symbol
  rescue StandardError
    Money::Currency.new('USD').symbol
  end
  discounted_unit_price = ci.discounted_price(target.customer, target)
  number_to_currency(discounted_unit_price * qty, unit: currency_symbol)
end

#display_discounted_line_total(li) ⇒ Object



341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
# File 'app/helpers/my_cart_helper.rb', line 341

def display_discounted_line_total(li)
  currency_symbol = begin
    li.resource.currency_symbol
  rescue StandardError
    Money::Currency.new('USD').symbol
  end
  # Only show discounts that have been applied through the coupon system
  # Do NOT calculate display-only tier pricing here - the coupon system handles that
  if li.is_discounted?
    tag.del(number_to_currency(li.price_total, unit: currency_symbol), class: 'text-muted') +
      (:span, number_to_currency(li.discounted_total, unit: currency_symbol), class: 'text-danger fw-bold')
  else
    # Wrap in span for consistent alignment with discounted prices
    (:span, number_to_currency(li.total, unit: currency_symbol), class: 'fw-bold')
  end
end

#display_discounted_shipping_price(order) ⇒ Object



305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
# File 'app/helpers/my_cart_helper.rb', line 305

def display_discounted_shipping_price(order)
  cart = order.is_a?(Order) ? order : @cart
  shipping_cost = cart.line_items.shipping_only.to_a.sum(&:price_total)
  discounted_shipping_cost = cart.discounted_shipping_total
  currency_symbol = begin
    order.currency_symbol
  rescue StandardError
    Money::Currency.new('USD').symbol
  end

  if shipping_cost == discounted_shipping_cost
    number_to_currency(shipping_cost, unit: currency_symbol)
  elsif discounted_shipping_cost.zero?
    tag.del(number_to_currency(shipping_cost, unit: currency_symbol), class: 'text-muted small me-1') +
      (:span, 'FREE', class: 'text-success fw-bold')
  else
    tag.del(number_to_currency(shipping_cost, unit: currency_symbol), class: 'text-muted small me-1') +
      (:span, number_to_currency(discounted_shipping_cost, unit: currency_symbol), class: 'text-danger fw-bold')
  end
end

#display_discounted_unit_price(li) ⇒ Object



200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'app/helpers/my_cart_helper.rb', line 200

def display_discounted_unit_price(li)
  currency_symbol = begin
    li.resource.currency_symbol
  rescue StandardError
    Money::Currency.new('USD').symbol
  end
  # Only show discounts that have been applied through the coupon system
  # Do NOT calculate display-only tier pricing here - the coupon system handles that
  if li.is_discounted?
    # `tag.del(...)` and `content_tag(...)` each return a SafeBuffer, but
    # interpolating them into a plain "..." string drops the html_safe flag
    # and the consumer (`<%= display_discounted_unit_price(li) %>`) then
    # HTML-escapes the whole thing — surfacing as literal `<del>$X</del>
    # <span class="text-danger fw-bold">$Y</span>` text in the cart UI.
    # `safe_join` concatenates while preserving html-safety.
    safe_join(
      [
        tag.del(number_to_currency(li.price, unit: currency_symbol)),
        (:span, number_to_currency(li.discounted_price, unit: currency_symbol), class: 'text-danger fw-bold')
      ],
      ' '
    )
  else
    number_to_currency(li.price, unit: currency_symbol)
  end
end

#display_discounted_unit_price_for_catalog_item(ci, target) ⇒ Object



358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
# File 'app/helpers/my_cart_helper.rb', line 358

def display_discounted_unit_price_for_catalog_item(ci, target)
  currency_symbol = begin
    target.currency_symbol
  rescue StandardError
    Money::Currency.new('USD').symbol
  end
  discounted_unit_price = ci.discounted_price(target.customer, target)
  if discounted_unit_price < ci.amount
    # See `display_discounted_unit_price` above: string interpolation
    # drops html_safe and surfaces literal `<del>...</del>` text in the UI.
    safe_join(
      [
        tag.del(number_to_currency(ci.amount, unit: currency_symbol)),
        (:span, number_to_currency(discounted_unit_price, unit: currency_symbol), class: 'text-danger fw-bold')
      ],
      ' '
    )
  else
    number_to_currency(ci.amount, unit: currency_symbol)
  end
end

#display_room_discounted_total(room) ⇒ Object



390
391
392
393
394
395
396
397
398
399
400
# File 'app/helpers/my_cart_helper.rb', line 390

def display_room_discounted_total(room)
  tot = 0.0
  room.line_items.each do |li|
    tot += li.quantity * li.catalog_item.discounted_price(room.customer, room)
  end
  number_to_currency(tot, unit: begin
    room.currency_symbol
  rescue StandardError
    Money::Currency.new('USD').symbol
  end)
end

#effective_cart_subtotal(cart) ⇒ Object

Calculate effective subtotal for cart display that accounts for sale prices
This ensures customers see promotional pricing in cart totals immediately



418
419
420
# File 'app/helpers/my_cart_helper.rb', line 418

def effective_cart_subtotal(cart)
  cart.line_items.active_non_shipping_lines.sum { |li| effective_line_total(li) }
end

#effective_cart_total(cart) ⇒ Object

Calculate effective total considering free economy shipping
This adjusts the total when economy shipping is selected but the discount hasn't been applied yet



333
334
335
336
337
338
339
# File 'app/helpers/my_cart_helper.rb', line 333

def effective_cart_total(cart)
  actual_shipping = cart.discounted_shipping_total
  effective_shipping = effective_shipping_cost(cart)
  shipping_adjustment = actual_shipping - effective_shipping

  cart.total - shipping_adjustment
end

#effective_line_total(li) ⇒ Object

Calculate effective line total considering sale prices and tier pricing
Returns the best price (lowest of: coupon-discounted, sale price, tier price, or regular)



404
405
406
407
408
409
410
411
412
413
414
# File 'app/helpers/my_cart_helper.rb', line 404

def effective_line_total(li)
  if li.is_discounted?
    li.discounted_total
  elsif li.catalog_item&.sale_price_in_effect?
    li.catalog_item.sale_price * li.quantity
  elsif (tier_price = calculate_tier_price_for_line_item(li))
    tier_price * li.quantity
  else
    li.total
  end
end

#effective_shipping_cost(cart) ⇒ Object

Calculate effective shipping cost considering free economy shipping



327
328
329
# File 'app/helpers/my_cart_helper.rb', line 327

def effective_shipping_cost(cart)
  cart.discounted_shipping_total
end

#expiration_month_for_ccObject



160
161
162
163
164
# File 'app/helpers/my_cart_helper.rb', line 160

def expiration_month_for_cc
  @cart.payments.last.credit_card.month
rescue StandardError
  nil
end

#expiration_year_for_ccObject



166
167
168
169
170
# File 'app/helpers/my_cart_helper.rb', line 166

def expiration_year_for_cc
  @cart.payments.last.credit_card.year
rescue StandardError
  nil
end

#format_cp_category(category) ⇒ Object



112
113
114
# File 'app/helpers/my_cart_helper.rb', line 112

def format_cp_category(category)
  category.capitalize.to_s
end

#format_cp_hint(category) ⇒ Object



116
117
118
119
120
121
122
123
124
125
# File 'app/helpers/my_cart_helper.rb', line 116

def format_cp_hint(category)
  case category
  when 'phone'
    '(___) ___-____'
  when 'fax'
    '(___) ___-____'
  when 'email'
    'e.g. JohnSmith@gmail.com'
  end
end

#has_tier_pricing?Boolean

Check if current user has tier pricing

Returns:

  • (Boolean)


292
293
294
295
296
# File 'app/helpers/my_cart_helper.rb', line 292

def has_tier_pricing?
  return false unless @context_user.respond_to?(:pricing_program_discount)

  @context_user.pricing_program_discount.to_f.positive?
end

#name_for_ccObject



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'app/helpers/my_cart_helper.rb', line 138

def name_for_cc
  n = o.payments.last&.credit_card&.name
  n = nil if n.blank?
  n ||= begin
    @cart.billing_address.person_name
  rescue StandardError
    nil
  end
  n ||= begin
    @cart.customer.contacts.last.full_name
  rescue StandardError
    nil
  end
  n
end

#number_for_ccObject



154
155
156
157
158
# File 'app/helpers/my_cart_helper.rb', line 154

def number_for_cc
  @cart.payments.last.credit_card.number
rescue StandardError
  nil
end

#payment_category_label(category) ⇒ Object



184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# File 'app/helpers/my_cart_helper.rb', line 184

def payment_category_label(category)
  label_msg = nil
  additional_info = nil
  case category
  when Payment::CREDIT_CARD
    label_msg = 'Pay using a Credit Card'
  when Payment::PO
    label_msg = 'Pay using a Purchase Order'
  when Payment::CHECK
    label_msg = 'I will send a check'
  when Payment::VPO
    label_msg = 'Pay using Verbal Purchase Order'
  end
  [label_msg, additional_info]
end

#safe_add_multiple_items_my_cart_path(items:) ⇒ Object

items is an array of "sku|qty" strings. If a SKU isn't orderable in the
current locale, fall back to its successor item's SKU when that one is.

Resolves orderability in 1-2 queries against the view_product_catalogs
materialized view (instead of N Item.find_by(sku:) + N
ViewProductCatalog Load round trips). For showcase/quote callers with 17+
SKUs this collapses ~34 queries into 1-2.



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'app/helpers/my_cart_helper.rb', line 11

def safe_add_multiple_items_my_cart_path(items:)
  parsed = items.filter_map do |sku_qty|
    sku, qty = sku_qty.to_s.split('|', 2)
    next if sku.blank? || qty.blank?

    [sku, qty]
  end
  skus = parsed.map(&:first).uniq
  return add_multiple_items_my_cart_path(items: []) if skus.empty?

  catalog_id = Catalog.locale_to_catalog_id

  # Single batched fetch across all catalogs. `successor_item_sku` is
  # denormalized identically on every row for a given `item_sku`, so any
  # row tells us the successor; the (catalog_id, item_is_web_accessible)
  # pair tells us whether the SKU itself is orderable in this locale.
  rows = ViewProductCatalog
         .where(item_sku: skus)
         .pluck(:item_sku, :catalog_id, :item_is_web_accessible, :successor_item_sku)

  orderable_skus = rows.each_with_object(Set.new) do |(sku, cid, accessible, _), set|
    set << sku if cid == catalog_id && accessible
  end
  successor_for = rows.each_with_object({}) do |(sku, _, _, succ_sku), hash|
    hash[sku] ||= succ_sku if succ_sku.present?
  end

  needed_successors = (skus - orderable_skus.to_a).filter_map { |s| successor_for[s] }.uniq
  successor_orderable = if needed_successors.any?
                          ViewProductCatalog
                            .where(item_sku: needed_successors,
                                   catalog_id: catalog_id,
                                   item_is_web_accessible: true)
                            .pluck(:item_sku).to_set
                        else
                          Set.new
                        end

  safe_items = parsed.filter_map do |sku, qty|
    if orderable_skus.include?(sku)
      "#{sku}|#{qty}"
    elsif (succ = successor_for[sku]) && successor_orderable.include?(succ)
      "#{succ}|#{qty}"
    end
  end

  add_multiple_items_my_cart_path(items: safe_items)
end

#setup_account(customer) ⇒ Object



99
100
101
102
103
# File 'app/helpers/my_cart_helper.rb', line 99

def (customer)
  customer.tap do |c|
    c. if c..nil?
  end
end

#setup_cart_for_checkout(cart) ⇒ Object



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'app/helpers/my_cart_helper.rb', line 83

def setup_cart_for_checkout(cart)
  cart.tap do |o|
    [ContactPoint::PHONE, ContactPoint::EMAIL].each do |cat|
      if o.customer.contact_points.none? { |cp| cp.category == cat }
        cp = o.customer.contact_points.build({ category: cat })
        cp.detail ||= .email if (cat == ContactPoint::EMAIL) &&  && .email
      end
    end
    # puts "!!!Contact Points in setup_cart_for_checkout, o.customer.contact_points: #{o.customer.contact_points.inspect}"
    o.customer.contacts.build if o.customer.contacts.empty?
    o.customer.build_billing_address(country_iso3: cart.catalog.store.country_iso3) unless o.customer.billing_address
    o.customer.clear_names if o.customer.has_guest_name?
    o.customer. if o.customer..nil?
  end
end

#setup_new_shipping_address(cart) ⇒ Object



105
106
107
108
109
110
# File 'app/helpers/my_cart_helper.rb', line 105

def setup_new_shipping_address(cart)
  cart.tap do |o|
    o.shipping_address = Address.new(party: o.customer, country_iso3: o.store.country_iso3) unless o.shipping_address&.new_record?
    o.shipping_address.person_name ||= o.customer.contacts.first&.full_name if o.customer.is_organization?
  end
end

#state_definition(state_name) ⇒ Object



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'app/helpers/my_cart_helper.rb', line 60

def state_definition(state_name)
  case state_name
  when :pending, :in_cr_hold, :in_management_hold
    'Your order was received and is awaiting review by our staff.'
  when :pending_payment
    'Your order is currently awaiting processing of payment information.'
  when :pending_release_authorization
    'Your order was received and is currently awaiting release by our finance department.'
  when :at_warehouse
    'Your order has been sent to our warehouse.'
  when :picking, :awaiting_deliveries
    'Your order was received by our warehouse staff and is currently being processed.'
  when :processing_deliveries
    'Your order has is partially shipped.'
  when :invoiced
    'Your order has been shipped.'
  when :cancelled
    'Your order was cancelled.'
  when :crm_back_order
    'Your order has been back ordered.'
  end
end

#tier_discount_percentageObject

Get tier discount percentage for current user



299
300
301
302
303
# File 'app/helpers/my_cart_helper.rb', line 299

def tier_discount_percentage
  return 0 unless @context_user.respond_to?(:pricing_program_discount)

  @context_user.pricing_program_discount.to_f
end

#validate_email_inline(category) ⇒ Object



127
128
129
130
131
132
133
134
135
136
# File 'app/helpers/my_cart_helper.rb', line 127

def validate_email_inline(category)
  case category
  when 'phone'
    ''
  when 'fax'
    ''
  when 'email'
    'required'
  end
end

#verification_value_for_ccObject



178
179
180
181
182
# File 'app/helpers/my_cart_helper.rb', line 178

def verification_value_for_cc
  @cart.payments.last.credit_card.verification_value
rescue StandardError
  nil
end