Module: Controllers::PriceEditable

Extended by:
ActiveSupport::Concern
Included in:
OrdersController
Defined in:
app/concerns/controllers/price_editable.rb

Overview

ActiveSupport::Concern mixin: price editable.

Instance Method Summary collapse

Instance Method Details

#price_editvoid

This method returns an undefined value.

Renders the price-editing form for the context object.

Only CO and ST order types support price editing; anything else is
redirected back to the object with an error flash.



18
19
20
21
22
23
24
25
26
27
# File 'app/concerns/controllers/price_editable.rb', line 18

def price_edit
  load_context
  authorize! :edit_prices, @context_object
  if %w[CO ST].include?(@context_object.order_type)
    render 'price_editable/price_edit'
  else
    flash[:error] = "#{@context_object.class.name} must be a CO or ST to allow price editing"
    redirect_to polymorphic_url(@context_object)
  end
end

#price_updatevoid

This method returns an undefined value.

Applies submitted unit price edits to the context object's line items.

Validates every submitted row first (discounted price, MSRP, optional
taxable amount); kit components and blank/invalid rows are skipped with
per-line and base errors. On success, assigns the new values, saves with
+do_not_set_totals+ so coupon recalculation does not wipe manual edits,
then refreshes header totals via +perform_db_total+.



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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'app/concerns/controllers/price_editable.rb', line 38

def price_update
  load_context
  authorize! :edit_prices, @context_object

  line_item_payload = params[:line_item]
  unless line_item_payload.respond_to?(:each)
    flash[:error] = 'No line item prices were submitted.'
    return render 'price_editable/price_edit'
  end

  applications = []
  line_item_payload.each do |line_item_id, values|
    next if values.blank?

    attrs = price_edit_line_item_attrs(values)
    discounted_raw = attrs['discounted_price'].to_s.strip
    price_raw = attrs['price'].to_s.strip

    line_item = @context_object.line_items.find { |li| li.id == line_item_id.to_i }
    next unless line_item
    # Kit components carry no price of their own — the kit parent owns it. The
    # form renders them read-only; guard the action too so a crafted payload
    # can't set one (see LineItem#enforce_zero_price_on_kit_component).
    next if line_item.kit_component?
    next if discounted_raw.blank? || price_raw.blank?

    new_taxable_raw = attrs['taxable_amount'].to_s.strip
    label = price_edit_line_item_label(line_item)
    row_invalid = false

    discounted_value = price_edit_parse_decimal(discounted_raw)
    if discounted_value.nil?
      line_item.errors.add(:discounted_price, 'is not a valid number')
      @context_object.errors.add(:base, "#{label}: unit discounted price is not a valid number")
      row_invalid = true
    end

    price_value = price_edit_parse_decimal(price_raw)
    if price_value.nil?
      line_item.errors.add(:price, 'is not a valid number')
      @context_object.errors.add(:base, "#{label}: unit MSRP is not a valid number")
      row_invalid = true
    end

    new_taxable_amount = nil
    if new_taxable_raw.present?
      tax_value = price_edit_parse_decimal(new_taxable_raw)
      if tax_value.nil?
        line_item.errors.add(:taxable_amount, 'is not a valid number')
        @context_object.errors.add(:base, "#{label}: unit taxable amount is not a valid number")
        row_invalid = true
      else
        new_taxable_amount = tax_value
      end
    end

    next if row_invalid

    applications << {
      line_item: line_item,
      discounted_price: discounted_value,
      price: price_value,
      taxable_amount: new_taxable_amount,
      tax_only: attrs['tax_only'].to_s == '1'
    }
  end

  if @context_object.errors.any?
    render 'price_editable/price_edit'
    return
  end

  applications.each do |app|
    li = app[:line_item]
    li.discounted_price = app[:discounted_price]
    li.price = app[:price]
    li.taxable_amount = app[:taxable_amount]
    li.tax_only = app[:tax_only]
  end

  if applications.any?
    # Saving with force_total_reset triggers set_totals → calculate_discounts, and
    # Coupon::ItemizableDiscountCalculator resets every line with discounted_price = price
    # (MSRP), wiping manual edits. Skip that path for this action only, then refresh
    # header totals via the same SQL used after a normal total reset.
    @context_object.do_not_set_totals = true if @context_object.respond_to?(:do_not_set_totals=)
    if @context_object.save
      @context_object.do_not_set_totals = false if @context_object.respond_to?(:do_not_set_totals=)
      @context_object.perform_db_total if @context_object.respond_to?(:perform_db_total)
      @context_object.reload
      redirect_to polymorphic_url(@context_object)
    else
      @context_object.do_not_set_totals = false if @context_object.respond_to?(:do_not_set_totals=)
      flash[:error] = @context_object.errors.full_messages.to_sentence.presence || 'Could not save price changes.'
      render 'price_editable/price_edit'
    end
  else
    flash[:error] = 'No prices were updated. Enter both unit discounted price and unit MSRP for each line you change.'
    render 'price_editable/price_edit'
  end
end