Class: Shipping::RlCarriersTracker
- Inherits:
-
Object
- Object
- Shipping::RlCarriersTracker
- Defined in:
- app/services/shipping/rl_carriers_tracker.rb
Overview
Polls R&L Carriers' Shipment Tracing SOAP service for a direct-R&L freight
delivery's PRO and projects the result into ShipmentEvent rows — the SAME
per-scan table parcel + ShipEngine LTL tracking write to. Reusing it means
the existing parcel status icon and Tracking Events tab light up with no new
UI: for direct R&L the PRO (WebProNumber) is stored on the delivery's
master_tracking_number AND on each shipment's tracking_number, so the
icon/tab (which key off shipment tracking numbers) already find these rows.
R&L has no tracking webhook, so this is poll-based — a near-clone of
ShipengineLtlTracker. Differences:
- keyed on
master_tracking_number(= WebProNumber), notltl_pro_number
(which is only set for the ShipEngine LTL carriers); - R&L returns a free-text status
Description(no status code), so we map
it via ShipmentEvent.infer_status_code — the same description→code
inference built for Canpar.
Stop conditions (see #pollable?): a delivered scan (DE/SP) on file, or the
POLL_MAX_AGE age backstop past pickup.
Constant Summary collapse
- CARRIER =
'RlCarriers'- SCAC =
R&L's SCAC (direct), stored on the event as carrier_code for parity with
the ShipEngine LTL path (informational only). 'RLCA'- POLL_MAX_AGE =
30.days
- TRACKING_TZ =
R&L returns Date + Time-of-day split, without an offset — parse in the
server's canonical display zone. 'America/Chicago'- DATE_TIME_FORMATS =
R&L dates are US-format MM/DD/YYYY and times are 12-hour with AM/PM
(e.g. "05/28/2026" / "05:59:02 PM"). Time.zone.parse misreads MM/DD/YYYY
as DD/MM/YYYY (verified live: "06/01/2026" → Jan 6, "05/28/2026" → error),
so parse with explicit strptime formats. Ordered most- to least-specific;
first match wins, nil if none (drop the scan rather than store a wrong date). ['%m/%d/%Y %I:%M:%S %p', '%m/%d/%Y %I:%M %p', '%m/%d/%Y %H:%M:%S', '%m/%d/%Y'].freeze
Class Method Summary collapse
Instance Method Summary collapse
-
#delivered? ⇒ Boolean
A delivered scan (DE/SP) is already on file for the PRO.
-
#event_rows(result) ⇒ Array<Hash>
Map an R&L trace result into ShipmentEvent attribute hashes — one per StatusHistory entry.
-
#initialize(delivery) ⇒ RlCarriersTracker
constructor
A new instance of RlCarriersTracker.
-
#past_backstop? ⇒ Boolean
Past the age backstop (pickup date, falling back to last-updated when pickup wasn't recorded).
-
#poll!(ignore_backstop: false) ⇒ Integer
Poll R&L and persist any new events.
- #pollable? ⇒ Boolean
Constructor Details
#initialize(delivery) ⇒ RlCarriersTracker
Returns a new instance of RlCarriersTracker.
53 54 55 56 |
# File 'app/services/shipping/rl_carriers_tracker.rb', line 53 def initialize(delivery) @delivery = delivery @pro = delivery.master_tracking_number.to_s.strip end |
Class Method Details
Instance Method Details
#delivered? ⇒ Boolean
Returns a delivered scan (DE/SP) is already on file for the PRO.
68 69 70 71 72 |
# File 'app/services/shipping/rl_carriers_tracker.rb', line 68 def delivered? return false if @pro.blank? ShipmentEvent.for_tracking_number(@pro).delivered.exists? end |
#event_rows(result) ⇒ Array<Hash>
Map an R&L trace result into ShipmentEvent attribute hashes — one per
StatusHistory entry. Status code is inferred from the free-text
description (R&L emits no code), reusing ShipmentEvent's inference.
104 105 106 107 |
# File 'app/services/shipping/rl_carriers_tracker.rb', line 104 def event_rows(result) data = result.is_a?(Hash) ? result.with_indifferent_access : {}.with_indifferent_access Array(data[:events]).filter_map { |event| event_row(event) } end |
#past_backstop? ⇒ Boolean
Returns past the age backstop (pickup date, falling back to
last-updated when pickup wasn't recorded).
76 77 78 79 |
# File 'app/services/shipping/rl_carriers_tracker.rb', line 76 def past_backstop? anchor = @delivery.confirmed_pickup_date || @delivery.updated_at&.to_date anchor.present? && anchor < POLL_MAX_AGE.ago.to_date end |
#poll!(ignore_backstop: false) ⇒ Integer
Poll R&L and persist any new events.
87 88 89 90 91 92 93 94 95 96 |
# File 'app/services/shipping/rl_carriers_tracker.rb', line 87 def poll!(ignore_backstop: false) return 0 if @pro.blank? return 0 if delivered? return 0 if !ignore_backstop && past_backstop? result = WyShipping.rl_carriers_tracking(@delivery) return 0 if result.blank? || !result[:success] persist_events(result) end |
#pollable? ⇒ Boolean
59 60 61 62 63 64 65 |
# File 'app/services/shipping/rl_carriers_tracker.rb', line 59 def pollable? return false if @pro.blank? return false if delivered? return false if past_backstop? true end |