Module: MyCartHelper
- Defined in:
- app/helpers/my_cart_helper.rb
Overview
View helper: my cart.
Instance Method Summary collapse
- #billing_zip_code_for_cc ⇒ Object
-
#calculate_tier_adjusted_total(itemizable) ⇒ Object
Calculate tier-adjusted total for an itemizable.
-
#calculate_tier_discount_total(itemizable) ⇒ Object
Calculate total tier discount for an itemizable (Quote/Order).
-
#calculate_tier_price_for_line_item(li) ⇒ Object
Calculate tier price for a line item if the current user has tier pricing.
- #cart_counter(_cart = nil) ⇒ Object
-
#cart_sale_price_savings(cart) ⇒ Object
Calculate total savings from sale prices and tier pricing (used in "You're saving" display).
- #checkout_breadcrumb(show_billing, show_delivery, show_payment) ⇒ Object
- #display_discounted_line_price_for_catalog_item(ci, qty, target) ⇒ Object
- #display_discounted_line_total(li) ⇒ Object
- #display_discounted_shipping_price(order) ⇒ Object
- #display_discounted_unit_price(li) ⇒ Object
- #display_discounted_unit_price_for_catalog_item(ci, target) ⇒ Object
- #display_room_discounted_total(room) ⇒ Object
-
#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.
-
#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.
-
#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).
-
#effective_shipping_cost(cart) ⇒ Object
Calculate effective shipping cost considering free economy shipping.
- #expiration_month_for_cc ⇒ Object
- #expiration_year_for_cc ⇒ Object
- #format_cp_category(category) ⇒ Object
- #format_cp_hint(category) ⇒ Object
-
#has_tier_pricing? ⇒ Boolean
Check if current user has tier pricing.
- #name_for_cc ⇒ Object
- #number_for_cc ⇒ Object
- #payment_category_label(category) ⇒ Object
-
#safe_add_multiple_items_my_cart_path(items:) ⇒ Object
items is an array of "sku|qty" strings.
- #setup_account(customer) ⇒ Object
- #setup_cart_for_checkout(cart) ⇒ Object
- #setup_new_shipping_address(cart) ⇒ Object
- #state_definition(state_name) ⇒ Object
-
#tier_discount_percentage ⇒ Object
Get tier discount percentage for current user.
- #validate_email_inline(category) ⇒ Object
- #verification_value_for_cc ⇒ Object
Instance Method Details
#billing_zip_code_for_cc ⇒ Object
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 (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, (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') + content_tag(: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 content_tag(: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') + content_tag(:span, 'FREE', class: 'text-success fw-bold') else tag.del(number_to_currency(shipping_cost, unit: currency_symbol), class: 'text-muted small me-1') + content_tag(: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)), content_tag(: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)), content_tag(: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_cc ⇒ Object
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_cc ⇒ Object
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
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_cc ⇒ Object
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_cc ⇒ Object
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 setup_account(customer) customer.tap do |c| c.build_account if c.account.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 ||= current_account.email if (cat == ContactPoint::EMAIL) && account_signed_in? && current_account.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.build_account if o.customer.account.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_percentage ⇒ Object
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_cc ⇒ Object
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 |