Class: ProductFilter::LineQualifier
- Inherits:
-
Object
- Object
- ProductFilter::LineQualifier
- Defined in:
- app/services/product_filter/line_qualifier.rb
Overview
Logic extraction for determining if a set of line items
qualify according to the rules set by an array of
product filters
Instance Attribute Summary collapse
-
#line_items ⇒ Object
readonly
Returns the value of attribute line_items.
-
#options ⇒ Object
readonly
Returns the value of attribute options.
-
#product_filters ⇒ Object
readonly
Returns the value of attribute product_filters.
-
#qty_min_repeat ⇒ Object
readonly
Returns the value of attribute qty_min_repeat.
Instance Method Summary collapse
- #code ⇒ Object
- #coupon_tier ⇒ Object
- #exclusion_level ⇒ Object
-
#initialize(line_items, product_filters, options = {}) ⇒ LineQualifier
constructor
A new instance of LineQualifier.
- #qualifying_line_items? ⇒ Boolean
Constructor Details
#initialize(line_items, product_filters, options = {}) ⇒ LineQualifier
Returns a new instance of LineQualifier.
7 8 9 10 11 12 |
# File 'app/services/product_filter/line_qualifier.rb', line 7 def initialize(line_items, product_filters, = {}) @line_items = line_items.reject(&:marked_for_destruction?) @product_filters = product_filters @options = @logger = [:logger] || Rails.logger end |
Instance Attribute Details
#line_items ⇒ Object (readonly)
Returns the value of attribute line_items.
5 6 7 |
# File 'app/services/product_filter/line_qualifier.rb', line 5 def line_items @line_items end |
#options ⇒ Object (readonly)
Returns the value of attribute options.
5 6 7 |
# File 'app/services/product_filter/line_qualifier.rb', line 5 def @options end |
#product_filters ⇒ Object (readonly)
Returns the value of attribute product_filters.
5 6 7 |
# File 'app/services/product_filter/line_qualifier.rb', line 5 def product_filters @product_filters end |
#qty_min_repeat ⇒ Object (readonly)
Returns the value of attribute qty_min_repeat.
5 6 7 |
# File 'app/services/product_filter/line_qualifier.rb', line 5 def qty_min_repeat @qty_min_repeat end |
Instance Method Details
#code ⇒ Object
22 23 24 |
# File 'app/services/product_filter/line_qualifier.rb', line 22 def code @options[:code] end |
#coupon_tier ⇒ Object
18 19 20 |
# File 'app/services/product_filter/line_qualifier.rb', line 18 def coupon_tier @options[:coupon_tier] end |
#exclusion_level ⇒ Object
14 15 16 |
# File 'app/services/product_filter/line_qualifier.rb', line 14 def exclusion_level @options[:exclusion_level] end |
#qualifying_line_items? ⇒ Boolean
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 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
# File 'app/services/product_filter/line_qualifier.rb', line 26 def # if we have an exclusive tier coupon and there's already a coupon of the same tier present, qualify is false if exclusion_level.to_s == 'exclusive_per_tier' && coupon_tier.present? && li = line_items.detect { |li| li.discounts.any? { |lid| lid.coupon.tier == coupon_tier && lid.coupon.code != code && lid.coupon.exclusion_level == exclusion_level.to_s } } @logger.debug "exclusive_per_tier: Already has a coupon of tier #{coupon_tier} on line id #{li.id}" return false end # Shortcut, true if any lines are present in case of empty product filters return @line_items.present? if @product_filters.empty? # Pre-compute: group line items by item and extract unique item IDs once # This avoids N+1 queries on item associations and enables targeted filter queries @lines_by_item ||= @line_items.group_by(&:item) candidate_item_ids = @lines_by_item.keys.map(&:id) # Go through each filter and attempt to match a line. Filters are all AND conditions on a given line and multiple filter must be met within the itemizable's lines qty_min_repeat_counters = [] = true @product_filters.each do |pf| pf.criteria_met = false total_qty_in_lines = 0 total_sqft_in_lines = 0.0 total_linear_ft_in_lines = 0.0 total_msrp_in_lines = 0.0 # Query only the candidate items (from order's line items) instead of ALL applicable items. # This is much faster: ~0.5-2ms for a targeted query vs ~1-5ms fetching potentially hundreds of IDs. matching_ids = pf.matching_item_ids(candidate_item_ids) # We group to tally up all lines for a given item. @lines_by_item.each do |item, lines| # If we have an exclusive coupon tier for this item on any of the line we skip the coupon if exclusion_level.to_s == 'exclusive_per_item_per_tier' && coupon_tier.present? && li = lines.detect { |li| li.discounts.any? { |lid| lid.coupon.tier == coupon_tier } } # If there is already a coupon on this item in this tier, ignore this qualifier and move to the next @logger.debug "exclusive_per_item_per_tier: #{item.id} already has a coupon of tier #{coupon_tier} on line id #{li.id}" next end # Check if this item matches the filter using the pre-computed matching IDs next unless matching_ids.include?(item.id) qty_in_lines = lines.sum(&:quantity).to_i total_qty_in_lines += qty_in_lines # Here we're going to skip over items that have length x width but focus only # on those that have coverage or sqft specified as a spec if item.has_spec?(:sqft) || item.has_spec?(:coverage) begin total_sqft_in_lines += qty_in_lines * item.sqft.to_f rescue StandardError => e ErrorReporting.error e, "Unable to calculate sqft for #{item.sku}" end end begin lin_ft = item.linear_ft || 0.0 total_linear_ft_in_lines += qty_in_lines * lin_ft rescue StandardError => e ErrorReporting.error e, "Unable to calculate lin_ft for #{item.sku}" end total_msrp_in_lines += (line_msrp_total = lines.sum(&:msrp_total)) end # Item quantities requirements item_qty_cond_met = ((pf.min_qty.nil? or total_qty_in_lines >= pf.min_qty) and (pf.max_qty.nil? or total_qty_in_lines <= pf.max_qty)) # Store how many times the item condition is met. qty_min_repeat_counters << (total_qty_in_lines / [pf.min_qty, 1].max).floor # item sq ft requirements total_sqft_in_lines = total_sqft_in_lines.ceil # Rounding up to next integer item_sqft_cond_met = ((pf.min_sq_ft.nil? or total_sqft_in_lines >= pf.min_sq_ft.to_i) and (pf.max_sq_ft.nil? or total_sqft_in_lines <= pf.max_sq_ft)) # item lin ft requirements total_linear_ft_in_lines = total_linear_ft_in_lines.ceil # Rounding up to next integer item_linear_ft_cond_met = ((pf.min_lin_ft.nil? or total_linear_ft_in_lines >= pf.min_lin_ft) and (pf.max_lin_ft.nil? or total_linear_ft_in_lines <= pf.max_lin_ft)) # item msrp amount requirements item_msrp_cond_met = ((pf.min_msrp_amount.nil? or total_msrp_in_lines >= pf.min_msrp_amount) and (pf.max_msrp_amount.nil? or total_msrp_in_lines <= pf.max_msrp_amount)) pf.criteria_met = (item_qty_cond_met and item_sqft_cond_met and item_msrp_cond_met and item_linear_ft_cond_met) @logger.debug "Product Filter #{pf.id} criteria met : #{pf.criteria_met}" = pf.criteria_met break unless # no need to continue processing if we are no longer qualifying. end @qty_min_repeat = qty_min_repeat_counters.min || 0 # Smallest occurence end |