Module: RmasHelper
- Includes:
- UrlsHelper
- Defined in:
- app/helpers/rmas_helper.rb
Overview
== Schema Information
Table name: rmas
id :integer not null, primary key
rma_number :string(255)
company_id :integer
original_order_id :integer
customer_id :integer
return_shipping_address_id :integer
customer_reference :string(255)
original_po_number :string(255)
contact_name :string(255)
description :string(255)
creator_id :integer
updater_id :integer
created_at :datetime
updated_at :datetime
payment_method :string(255)
state :string(255)
uploads_count :integer
transmission_state :string(255)
legacy_transmission_email :string(255)
legacy_transmission_fax :string(255)
return_label_required :boolean not null
ship_from_address_id :integer
delivery_id :integer
transmission_email :string(255) default([]), is an Array
transmission_fax :string(255) default([]), is an Array
serial_number_state :string
Constant Summary collapse
- RMA_MILESTONE_LABELS =
Labels for +Rma#versions_for_dates_tracker+ keys (executive-facing, sentence case).
{ dropped_off: 'In transit to warehouse', warehouse_received: 'Warehouse received', submitted_for_inspection: 'Submitted for inspection', returned: 'Returned', process_refund: 'Refund processing', refunded: 'Refunded' }.freeze
- RMA_MILESTONE_COMPLETED_BY =
{ dropped_off: %i[warehouse_received], warehouse_received: %i[submitted_for_inspection returned], submitted_for_inspection: %i[returned], returned: %i[process_refund], process_refund: %i[refunded], refunded: %i[fully_refunded] }.freeze
- CANADA_DEFAULT_CARRIER =
Carrier whose option is pre-selected by default on Canadian RMA returns.
Matches the +carrier+ prefix of the labels built by
+ShippingOption.supported_carrier_options_for_rmas+ (e.g. "Canadapost: …"). 'Canadapost'
Instance Method Summary collapse
-
#cheapest_shipping_option_id(shipping_options, shipping_costs) ⇒ Object
Returns the +shipping_option_id+ with the lowest positive quoted +cost+, or +nil+ when none qualify.
-
#compute_returned_kit_quantity(kit_li, rma_item) ⇒ Object
Kit component qty shown on RMA line rows: scales +kit_li.quantity+ by returned subset.
-
#default_shipping_option_id(country_iso, shipping_options, shipping_costs) ⇒ Object
Returns the +shipping_option_id+ to pre-select in the RMA shipping picker.
- #rma_command_options(rma) ⇒ Object
- #rma_item_status_css_classes(rma_item) ⇒ Object
-
#rma_kit_grouped_lines(rows) ⇒ Object
Order return-delivery lines so each kit parent is immediately followed by its own components, instead of the id order the +expand_kit+ trigger and the packing algorithm leave behind (kit line stranded below its own components, which reads as unrelated duplicate SKUs on the shipping-costs page).
-
#rma_kit_line_badge(line_item) ⇒ Object
Pill markup flagging a return line as a kit or as one of a kit's components.
-
#rma_milestone_backfill_dates(dates_tracker, phases) ⇒ void
Backfill nil dates for steps promoted to +:complete+ by the backward sweep.
-
#rma_milestone_date_caption(date) ⇒ String
Formats one milestone date for the CRM tracker.
-
#rma_milestone_enriched_dates(dates_tracker, rma: nil) ⇒ Hash{Symbol => Date, nil}
Dup of +versions_for_dates_tracker+ with display-only enrichment: 1.
- #rma_milestone_label(key) ⇒ Object
-
#rma_milestone_phases(dates_tracker, state: nil) ⇒ Hash{Symbol => Symbol}
Each visible milestone key => +:complete+, +:current+ (in-progress), or +:upcoming+.
- #rma_payment_status(rma) ⇒ Object
-
#rma_return_delivery_status_badge_class(delivery) ⇒ Object
Bootstrap +text-bg-*+ for return +Delivery+ state pills on the RMA return-labels tab.
- #rma_returned_reasons_with_explanations(returned_reasons = []) ⇒ Object
-
#rma_shipsurance_links(shipment) ⇒ Object
Safe Shipsurance / Loadsure links for an insured return shipment (+link_to+, +sanitize_external_url+, +data-turbo-confirm+ on claim — no +html_safe+, no inline +onclick+).
-
#sorted_rma_items_for_show_table(rma_items) ⇒ Object
Sort RMA lines by state machine order (same ordering as the legacy items tab table).
Methods included from UrlsHelper
#catalog_breadcrumb_links, #catalog_link, #catalog_link_for_product_line, #catalog_link_for_sku, #cms_link, #delocalized_path, #path_to_sales_product_sku, #path_to_sales_product_sku_for_product_line, #path_to_sales_product_sku_for_product_line_slug, #product_line_from_catalog_link, #protocol_neutral_url, #sanitize_external_url, #valid_external_url?
Instance Method Details
#cheapest_shipping_option_id(shipping_options, shipping_costs) ⇒ Object
Returns the +shipping_option_id+ with the lowest positive quoted +cost+, or +nil+ when none qualify.
+shipping_options+ — array of +[label, shipping_option_id]+ pairs (e.g. from +ShippingOption.supported_carrier_options_for_rmas+).
+shipping_costs+ — hash keyed by option id to objects responding to +#cost+ (may be +nil+; treated as empty).
220 221 222 223 224 225 226 227 228 229 230 231 |
# File 'app/helpers/rmas_helper.rb', line 220 def cheapest_shipping_option_id(, shipping_costs) return nil if .blank? costs_by_id = shipping_costs || {} = .map { |(_, id)| [id, costs_by_id[id]&.cost] } .select { |(_, cost)| cost.present? && cost.to_f > 0 } .map { |(id, cost)| [id, cost.to_f] } .min_by { |(_, cost)| cost }&.first end |
#compute_returned_kit_quantity(kit_li, rma_item) ⇒ Object
Kit component qty shown on RMA line rows: scales +kit_li.quantity+ by returned subset.
Returns +0+ when the parent line has no positive quantity (avoids division by zero).
351 352 353 354 355 356 357 358 359 360 |
# File 'app/helpers/rmas_helper.rb', line 351 def compute_returned_kit_quantity(kit_li, rma_item) parent = rma_item.returned_line_item return 0 unless parent denom = parent.quantity return 0 if denom.nil? || !denom.to_f.positive? numerator = kit_li.quantity.to_i * rma_item.returned_item_quantity.to_i numerator / denom end |
#default_shipping_option_id(country_iso, shipping_options, shipping_costs) ⇒ Object
Returns the +shipping_option_id+ to pre-select in the RMA shipping picker.
Canadian RMAs default to the Canada Post option; every other country (and
a Canadian RMA that happens to have no Canada Post option) falls back to
the cheapest quoted rate via +cheapest_shipping_option_id+.
+country_iso+ — the RMA's origin country ISO (e.g. "CA", "US").
+shipping_options+ — array of +[label, shipping_option_id]+ pairs.
+shipping_costs+ — hash keyed by option id to objects responding to +#cost+.
247 248 249 250 251 252 253 254 255 256 |
# File 'app/helpers/rmas_helper.rb', line 247 def default_shipping_option_id(country_iso, , shipping_costs) if country_iso == 'CA' canada_post = Array().find do |(label, _id)| label.to_s.start_with?("#{CANADA_DEFAULT_CARRIER}:") end return canada_post.last if canada_post end cheapest_shipping_option_id(, shipping_costs) end |
#rma_command_options(rma) ⇒ Object
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 |
# File 'app/helpers/rmas_helper.rb', line 258 def (rma) opts = [] opts << fa_icon('right-to-bracket', text: rma.human_state_name.titleize) opts << link_to('Edit', edit_rma_path(rma)) if rma.can_be_edited? opts << link_to('Add Items', add_items_rma_path(rma)) if rma.requested? opts << link_to('Edit Items', edit_items_rma_path(rma)) if rma.can_be_edited? && rma.has_rma_items_requested? opts << link_to('Generate Return Labels', edit_return_labels_rma_path(rma)) if rma.can_be_edited? && rma.has_rma_items_requested? && rma.return_delivery.blank? && rma.number_of_return_labels_required.zero? opts << link_to('Generate New Return Labels', edit_return_labels_rma_path(rma)) if rma.can_be_edited? && rma.has_rma_items_requested? && rma.return_delivery.present? && !rma.number_of_return_labels_required.zero? if rma.requested? && !rma.number_of_return_labels_required.zero? && rma.return_delivery.present? opts << link_to('Reset Shipping State', reset_shipping_state_rma_path(rma), data: { turbo_confirm: 'Are you sure you want to reset the shipping state? This will remove return label requirements and advance the RMA to awaiting_return state.', turbo_method: :put }) end opts << link_to('Void RMA', void_rma_rma_path(rma), data: { turbo_confirm: 'Are you sure you want to void this RMA? This will cancel any credit orders and return labels.', turbo_method: :put }) if rma.can_be_voided? opts << link_to('Approve RMA', workflow_action_rma_path(rma, wf_action: 'review_complete')) if rma.auto_return_review? && user_has_role?(%w[admin accounting_manager accounting_rep]) if rma.can_be_unreturned? && can?( :unreturn_rma, rma ) opts << link_to('Unreturn RMA', unreturn_rma_rma_path(rma), data: { turbo_confirm: 'Are you sure you want to unreturn this RMA? This will reverse the item ledger entries and make the items returnable or voidable again.', turbo_method: :put }) end opts << link_to('Receive/Inspect Items', receive_items_rma_path(rma)) if rma.can_be_received? && rma.rma_items.returnable.present? opts << link_to('Transmit', transmit_rma_path(rma)) if rma.can_be_transmitted? opts << link_to('Print Serial Number Labels', print_serial_number_labels_rma_path(rma)) if rma.serial_numbers.where(print_state: 'pending_print').exists? if rma.voided? # opts << { content: 'No actions available' } if rma.rma_items.present? opts << link_to('Unvoid RMA', unvoid_rma_rma_path(rma), data: { turbo_confirm: 'Are you sure you want to unvoid this RMA?', turbo_method: :put }) if rma.can_be_unvoided? else opts << { content: 'No actions available' } end end opts end |
#rma_item_status_css_classes(rma_item) ⇒ Object
362 363 364 365 366 367 368 369 370 371 372 373 374 375 |
# File 'app/helpers/rmas_helper.rb', line 362 def rma_item_status_css_classes(rma_item) state = rma_item.state&.to_s modifier = case state when 'returned' 'text-bg-success' when 'voided' 'text-bg-danger' when 'awaiting_inspection' 'text-bg-warning' else 'text-bg-info' end "badge rounded-pill #{modifier} fw-semibold" end |
#rma_kit_grouped_lines(rows) ⇒ Object
Order return-delivery lines so each kit parent is immediately followed by its
own components, instead of the id order the +expand_kit+ trigger and the
packing algorithm leave behind (kit line stranded below its own components,
which reads as unrelated duplicate SKUs on the shipping-costs page).
+rows+ — +LineItem+s, or wrappers such as +ShipmentContent+ when a block is
given to map each row to its +LineItem+.
299 300 301 302 303 304 |
# File 'app/helpers/rmas_helper.rb', line 299 def rma_kit_grouped_lines(rows) rows.sort_by do |row| line = block_given? ? yield(row) : row [line&.parent_id || line&.id || 0, line&.parent_id ? 1 : 0] end end |
#rma_kit_line_badge(line_item) ⇒ Object
Pill markup flagging a return line as a kit or as one of a kit's components.
Text-labelled, not color-only, so the distinction survives grayscale.
Returns +nil+ for ordinary lines.
309 310 311 312 313 314 315 316 317 |
# File 'app/helpers/rmas_helper.rb', line 309 def rma_kit_line_badge(line_item) return if line_item.blank? if line_item.is_kit? tag.span('Kit', class: 'badge rounded-pill text-bg-info fw-normal align-middle') elsif line_item.parent_id.present? tag.span('Kit component', class: 'badge rounded-pill text-bg-light border text-body-secondary fw-normal align-middle') end end |
#rma_milestone_backfill_dates(dates_tracker, phases) ⇒ void
This method returns an undefined value.
Backfill nil dates for steps promoted to +:complete+ by the backward sweep.
Uses the closest subsequent step's date so the card shows when the milestone
was implicitly passed rather than "—".
193 194 195 196 197 198 199 200 201 |
# File 'app/helpers/rmas_helper.rb', line 193 def rma_milestone_backfill_dates(dates_tracker, phases) visible_keys = dates_tracker.keys - [:fully_refunded] visible_keys.each_with_index do |key, idx| next unless phases[key] == :complete && dates_tracker[key].blank? later_date = visible_keys[(idx + 1)..].lazy.map { |k| dates_tracker[k] }.find(&:present?) dates_tracker[key] = later_date if later_date end end |
#rma_milestone_date_caption(date) ⇒ String
Formats one milestone date for the CRM tracker.
207 208 209 210 211 212 213 214 |
# File 'app/helpers/rmas_helper.rb', line 207 def rma_milestone_date_caption(date) if date.present? d = date.respond_to?(:to_date) ? date.to_date : date d.strftime('%b %d, %Y') else '—' end end |
#rma_milestone_enriched_dates(dates_tracker, rma: nil) ⇒ Hash{Symbol => Date, nil}
Dup of +versions_for_dates_tracker+ with display-only enrichment:
- If +warehouse_received+ is blank but +submitted_for_inspection+ has a date, copy it (receipt precedes inspection).
- For +dropped_off+ (in transit): prefer return label +Shipment#date_shipped+ when +rma+ is given; else backfill from +warehouse_received+ when missing.
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
# File 'app/helpers/rmas_helper.rb', line 119 def rma_milestone_enriched_dates(dates_tracker, rma: nil) return dates_tracker if dates_tracker.blank? h = dates_tracker.dup if h.key?(:warehouse_received) && h[:warehouse_received].blank? && h[:submitted_for_inspection].present? insp = h[:submitted_for_inspection] h[:warehouse_received] = insp.respond_to?(:to_date) ? insp.to_date : insp end if h.key?(:dropped_off) label_date = rma&.earliest_return_label_ship_date if label_date.present? h[:dropped_off] = label_date.respond_to?(:to_date) ? label_date.to_date : label_date elsif h[:dropped_off].blank? && h[:warehouse_received].present? h[:dropped_off] = h[:warehouse_received] end end h end |
#rma_milestone_label(key) ⇒ Object
108 109 110 |
# File 'app/helpers/rmas_helper.rb', line 108 def rma_milestone_label(key) RMA_MILESTONE_LABELS[key.to_sym] || key.to_s.humanize end |
#rma_milestone_phases(dates_tracker, state: nil) ⇒ Hash{Symbol => Symbol}
Each visible milestone key => +:complete+, +:current+ (in-progress), or +:upcoming+.
Phase is determined by checking what subsequent transition has occurred,
not simply whether the step itself has a date. +:fully_refunded+ is metadata
only and excluded from the returned hash.
After the per-step pass a backward sweep guarantees that if any later step
is +:current+ or +:complete+, every earlier step is promoted to +:complete+.
This handles state-machine skips (e.g. RMA jumps from +returned+ straight
to +credited_fully_refunded+ without passing through +credit_in_process+).
151 152 153 154 155 156 |
# File 'app/helpers/rmas_helper.rb', line 151 def rma_milestone_phases(dates_tracker, state: nil) return {} if dates_tracker.blank? state_phases = rma_milestone_phases_from_state(dates_tracker, state) if state.present? state_phases || rma_milestone_phases_from_dates(dates_tracker) end |
#rma_payment_status(rma) ⇒ Object
385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 |
# File 'app/helpers/rmas_helper.rb', line 385 def rma_payment_status(rma) return nil if rma.payment_status.nil? tag.div(class: 'rma-payment-status') do safe_join( rma.payment_status.map do |status| text_class = rma_payment_status_text_classes(status[:color]) tag.div(class: 'rma-payment-status__line') do tag1 = tag.span(class: text_class) do if rma.payment_status.length > 1 "#{status[:ref]}: #{status[:status]}" else status[:status] end end if status[:preview] tag1 + tag.span(class: 'ms-1 align-middle') do link_to(fa_icon('file-lines'), status[:preview], target: '_blank', rel: 'noopener') end else tag1 end end end ) end end |
#rma_return_delivery_status_badge_class(delivery) ⇒ Object
Bootstrap +text-bg-*+ for return +Delivery+ state pills on the RMA return-labels tab.
320 321 322 323 324 325 326 327 328 329 330 331 332 333 |
# File 'app/helpers/rmas_helper.rb', line 320 def rma_return_delivery_status_badge_class(delivery) return 'text-bg-secondary' if delivery.blank? case delivery.state.to_s when 'return_labels_complete', 'shipped', 'invoiced' 'text-bg-success' when 'pending_ship_labels', 'pending_ship_confirm', 'pending_manifest_completion', 'pending_carrier_confirm', 'pending_pickup_confirm' 'text-bg-warning' when 'cancelled' 'text-bg-danger' else 'text-bg-secondary' end end |
#rma_returned_reasons_with_explanations(returned_reasons = []) ⇒ Object
377 378 379 380 381 382 383 |
# File 'app/helpers/rmas_helper.rb', line 377 def rma_returned_reasons_with_explanations(returned_reasons = []) content_tag(:ul, class: 'list-inline') do RmaReasonCode.where(code: returned_reasons).order(:code).each do |r| concat content_tag(:li, r.code, data: { 'bs-toggle': 'tooltip', title: r.description }) end end end |
#rma_shipsurance_links(shipment) ⇒ Object
Safe Shipsurance / Loadsure links for an insured return shipment (+link_to+, +sanitize_external_url+,
+data-turbo-confirm+ on claim — no +html_safe+, no inline +onclick+).
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 |
# File 'app/helpers/rmas_helper.rb', line 39 def rma_shipsurance_links(shipment) id = shipment.shipping_insurance_record_id return if id.blank? enc = ERB::Util.url_encode(id.to_s) ltl = shipment.delivery&.ships_ltl_freight? view_raw, claim_raw = if ltl [ "https://portal.loadsure.net/certificates/details/?certificateNumber=#{enc}", "https://portal.loadsure.net/claims/file?id=#{enc}" ] else [ "https://app.shipsurance.com/cp/ViewRecordedshipments?RecordedShipmentId=#{enc}", "https://app.shipsurance.com/cp/CreateClaim?recordedShipmentId=#{enc}" ] end view_href = sanitize_external_url(view_raw) claim_href = sanitize_external_url(claim_raw) record_link = if view_href.present? link_to(id.to_s, view_href, target: "_blank", rel: "noopener noreferrer") else id.to_s end claim_suffix = if claim_href.present? tag.small(class: "text-body-secondary ms-1") do safe_join( [ "(file claim ", link_to( "here", claim_href, target: "_blank", rel: "noopener noreferrer", data: { turbo_confirm: "Are you sure you want to file a claim?" } ), ")" ], "" ) end else "".html_safe end safe_join([record_link, claim_suffix], " ") end |
#sorted_rma_items_for_show_table(rma_items) ⇒ Object
Sort RMA lines by state machine order (same ordering as the legacy items tab table).
336 337 338 339 340 341 342 343 344 345 346 347 |
# File 'app/helpers/rmas_helper.rb', line 336 def sorted_rma_items_for_show_table(rma_items) return [] if rma_items.blank? order = RmaItem.state_machines[:state].states.map(&:name) fallback = order.length rma_items.to_a.sort_by do |ri| state = ri.state next fallback if state.blank? order.index(state.to_sym) || fallback end end |