Class: Shipping::LtlShippingInsurance

Inherits:
BaseService show all
Defined in:
app/services/shipping/ltl_shipping_insurance.rb

Defined Under Namespace

Classes: Result

Constant Summary collapse

LOADSURE_API_HASH =

LOADSURE_API_HASH = {
base_url: LOADSURE_API_BASE_URL,
partner_id: LOADSURE_API_PARTNER_ID,
user_id: LOADSURE_API_USER_ID,
additional_user_id: LOADSURE_API_ADDITIONAL_USER_ID,
token: LOADSURE_API_TOKEN
}

{}
INSURED_VALUE_MIN =

LOADSURE_API_HASH = Heatwave::Configuration.fetch(:loadsurance_api)

5000.0
INSURANCE_FEE_MAX_PERCENT =
0.05
INSURANCE_FEE_MAX_AMOUNT =
250.0
INSURANCE_DED_MAX_PERCENT =
0.2
INSURANCE_DED_MAX_AMOUNT =
1000.0
INSURANCE_FEE_MIN_AMOUNT =
5.0
LOADSURANCE_CARRIER_DATA_BY_CARRIER =

here we only populate parcel carriers that we know are covered by our Shipsurance contract
we do not cover freight carriers per Shipsurance contract
see: https://shipsurance-partner-api.readme.io/reference/extcarrierid-lookup

{
  FedExFreight: {
    mode: 'ROAD',
    name: 'FedEx Freight',
    email: 'mark.lackinger@fedex.com',
    carrierId: {
      type: 'SCAC',
      value: 'FXFS'
    },
    phone: '+12242422831',
    address: {
      address1: '5600 9TH ST',
      city: 'Zion',
      state: 'IL',
      postal: '60099',
      country: 'US'
    }
  },
  Freightquote: {
    mode: 'ROAD',
    name: 'Freightquote.com Inc',
    email: 'brian.dougherty@freightquote.com',
    carrierId: {
      type: 'SCAC',
      value: 'FQCI'
    },
    phone: '+18169496250',
    address: {
      address1: '901 West Carondelet Drive',
      city: 'Kansas City',
      state: 'MO',
      postal: '64114',
      country: 'US'
    }
  },
  RlCarriers: {
    mode: 'ROAD',
    name: 'R&L Carriers',
    email: 'scott.brighton@rlcarriers.com',
    carrierId: {
      type: 'SCAC',
      value: 'RLCA'
    },
    phone: '+18476953100',
    address: {
      address1: '375 Second Street',
      city: 'Elgin',
      state: 'IL',
      postal: '60123',
      country: 'US'
    }
  },
  Roadrunner: {
    mode: 'ROAD',
    name: 'Roadrunner Freight',
    email: 'robert.provost@rrts.com',
    carrierId: {
      type: 'SCAC',
      value: 'RDFS'
    },
    phone: '+18478007768',
    address: {
      address1: '4801 68th Avenue',
      city: 'Kenosha',
      state: 'WI',
      postal: '53144',
      country: 'US'
    }
  },
  Saia: {
    mode: 'ROAD',
    name: 'Saia LTL Freight',
    email: 'ssanti@saia.com',
    carrierId: {
      type: 'SCAC',
      value: 'SAIA'
    },
    phone: '+18474716588',
    address: {
      address1: '2260 Midlothian Road',
      city: 'Grayslake',
      state: 'IL',
      postal: '60030',
      country: 'US'
    }
  }
}
COVERAGE_LIMIT =
25_000.0

Instance Method Summary collapse

Methods inherited from BaseService

#initialize, #log_debug, #log_error, #log_info, #log_warning, #logger, #options, #tagged_logger

Constructor Details

This class inherits a constructor from BaseService

Instance Method Details

#bill_shipping_to_customer_and_not_a_store_transfer?(delivery) ⇒ Boolean

Returns:

  • (Boolean)


148
149
150
151
# File 'app/services/shipping/ltl_shipping_insurance.rb', line 148

def bill_shipping_to_customer_and_not_a_store_transfer?(delivery)
  # if billing to customer, let them pay for declared value via carrier
  delivery.bill_shipping_to_customer && !delivery.order&.is_store_transfer? # this is to be readable to me, a human
end

#book_shipping_insurance_for_delivery(delivery, options) ⇒ Object



196
197
198
199
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
226
227
# File 'app/services/shipping/ltl_shipping_insurance.rb', line 196

def book_shipping_insurance_for_delivery(delivery, options)
  status = :ok
  status_message = ''
  insurance_result_hash = {}

  quote_hash = HTTP.use(logging: { logger: Rails.logger }).timeout(60).auth("Bearer #{LOADSURE_API_HASH[:token]}").post("#{LOADSURE_API_HASH[:base_url]}/quote", json: quote_data(delivery)).parse.with_indifferent_access
  if quote_hash[:success] == false
    msgs = (quote_hash[:errors] || []).map { |e| e.dig(:message) }
    status = :error
    status_message = msgs.join('. ')
  else
    quote_cost = quote_hash.dig(:insuranceProduct, :premium).to_f + quote_hash.dig(:insuranceProduct, :serviceFee).to_f
    quote_deductible = quote_hash.dig(:insuranceProduct, :deductible).to_f
    threshhold_exceeded_hash = insurance_quote_exceeds_thresholds?(quote_cost, apply_carrier_coverage_limit(delivery), quote_deductible)
    quote_token = quote_hash.dig(:quoteToken)
    if threshhold_exceeded_hash[:status] != :ok
      status = threshhold_exceeded_hash[:status]
      status_message = threshhold_exceeded_hash[:status_message]
    elsif quote_token.present?
      insurance_result_hash = HTTP.use(logging: { logger: Rails.logger }).timeout(60).auth("Bearer #{LOADSURE_API_HASH[:token]}").post("#{LOADSURE_API_HASH[:base_url]}/purchaseQuote",
                                                                                                                                       json: { quoteToken: quote_token,
                                                                                                                                               additionalAuthorizedUsers: [LOADSURE_API_HASH[:additional_user_id]] }).parse.with_indifferent_access
      if insurance_result_hash[:error].present?
        status = :error
        status_message = insurance_result_hash[:error]
      end
    end
  end

  delivery.shipments.update_all(shipping_insurance_data: { shipping_insurance_record_id: insurance_result_hash[:certificateNumber] }.merge(insurance_result_hash)) if status == :ok
  Result.new(status: status, status_message: status_message)
end

#carrier_present?(carrier) ⇒ Boolean

Returns:

  • (Boolean)


158
159
160
# File 'app/services/shipping/ltl_shipping_insurance.rb', line 158

def carrier_present?(carrier)
  LOADSURANCE_CARRIER_DATA_BY_CARRIER[(carrier || 'none').to_sym].present?
end

#carrier_qualifies_for_rating?(carrier) ⇒ Boolean

Returns:

  • (Boolean)


166
167
168
169
# File 'app/services/shipping/ltl_shipping_insurance.rb', line 166

def carrier_qualifies_for_rating?(carrier)
  # here we want to return a simple TRUE/FALSE boolean, this is to see if we can use this
  carrier_present?(carrier)
end

#cross_border_fedex_freight?(delivery) ⇒ Boolean

Returns:

  • (Boolean)


153
154
155
156
# File 'app/services/shipping/ltl_shipping_insurance.rb', line 153

def cross_border_fedex_freight?(delivery)
  # we do not ship insure cross border FedEx Freight via Loadsurance because we need to pass declared value
  delivery.carrier == 'FedExFreight' && delivery.is_cross_border? # this is to be readable to me, a human
end

#delivery_carrier_present?(delivery) ⇒ Boolean

Returns:

  • (Boolean)


162
163
164
# File 'app/services/shipping/ltl_shipping_insurance.rb', line 162

def delivery_carrier_present?(delivery)
  carrier_present?(delivery.carrier)
end

#get_shipping_insurance_cost_for_delivery(delivery) ⇒ Object



244
245
246
247
# File 'app/services/shipping/ltl_shipping_insurance.rb', line 244

def get_shipping_insurance_cost_for_delivery(delivery)
  delivery.shipments.first&.shipping_insurance_data&.dig('premium').to_f
  # cost = delivery.shipments.where("carrier IS NOT NULL").sum{|s| get_shipping_insurance_cost_for_shipment(s)}
end

#get_shipping_insurance_cost_for_shipment(shipment) ⇒ Object



249
250
251
# File 'app/services/shipping/ltl_shipping_insurance.rb', line 249

def get_shipping_insurance_cost_for_shipment(shipment)
  (get_shipping_insurance_cost_for_delivery(shipment.delivery) / [shipment.delivery.shipments.size, 1].max).round(2)
end

#get_shipping_insurance_insured_value_for_delivery(delivery) ⇒ Object



253
254
255
# File 'app/services/shipping/ltl_shipping_insurance.rb', line 253

def get_shipping_insurance_insured_value_for_delivery(delivery)
  delivery.calculate_declared_value.to_f.abs
end

#get_shipping_insurance_insured_value_for_shipment(shipment) ⇒ Object



257
258
259
# File 'app/services/shipping/ltl_shipping_insurance.rb', line 257

def get_shipping_insurance_insured_value_for_shipment(shipment)
  (shipment.delivery.calculate_declared_value.to_f.abs / [shipment.delivery.shipments.size, 1].max).round(2)
end


261
262
263
# File 'app/services/shipping/ltl_shipping_insurance.rb', line 261

def get_shipping_insurance_link_for_shipment(shipment)
  %(<a href='https://portal.loadsure.net/certificates/details/?certificateNumber=#{shipment.shipping_insurance_record_id}' target='_blank' rel='noopener'>#{shipment.shipping_insurance_record_id}</a> <small>(file claim <a href='https://portal.loadsure.net/claims/file?id=#{shipment.shipping_insurance_record_id}' target='_blank' rel='noopener' onclick='return confirm("Are you sure you want to file a claim?")'>here</a>)</small>).html_safe
end

#process(delivery, options = {}) ⇒ Object



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'app/services/shipping/ltl_shipping_insurance.rb', line 117

def process(delivery, options = {})
  status = :ok
  status_message = 'WarmlyYours Self Insures all LTL Shipping'
  qualifies_res = qualifies(delivery)
  # 03/18/24 Here we have decided per Venu and Christian to discontinue our Shipping Insurance with Loadsure, and self-insure after seeing no record of any claims or losses for LTL shipments in our history.
  # So we want to qualify all LTL shipments i.e. return TRUE, so we don't pass declared value to the LTL carriers, but then do nothing when it is ship insurance time
  # if qualifies_res.status == :ok
  #   r = book_shipping_insurance_for_delivery(delivery, options)
  #   unless r.status == :ok
  #     status = :error
  #     status_message = "Shipping insurance for delivery #{delivery.name} cannot be booked: #{r.status_message}"
  #   end
  # else
  #   status = qualifies_res.status
  #   status_message = qualifies_res.status_message
  # end
  Result.new(status: status, status_message: status_message)
end

#qualifies(delivery) ⇒ Object



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'app/services/shipping/ltl_shipping_insurance.rb', line 171

def qualifies(delivery)
  # status = :ok
  # status_message = ''
  # any_labeled_shipments = delivery.shipments.label_complete.any?
  # all_labeled_shipments_have_tracking_number = delivery.shipments.label_complete.all?{|s| s.tracking_number.present?}
  # unless qualifies_for_rating?(delivery) && any_labeled_shipments && all_labeled_shipments_have_tracking_number
  #   status = :error
  #   status_message_arr = []
  #   status_message_arr << "Order #{delivery.order&.reference_number} does not qualify for Loadsurance shipping insurance"
  #   status_message_arr << "Carrier #{delivery.carrier} is not one of our covered Loadsurance carriers" if !delivery_carrier_present?(delivery)
  #   status_message_arr << "When customer pays for shipping we do not ship insure via Loadsurance" if bill_shipping_to_customer_and_not_a_store_transfer?(delivery)
  #   status_message_arr << "When delivery value is less than $#{INSURED_VALUE_MIN.round(2)} we do not ship insure via Loadsurance" if delivery.calculate_declared_value.to_f.abs < INSURED_VALUE_MIN
  #   status_message_arr << "When delivery is FedExFreight cross-border, we do not ship insure via Loadsurance" if cross_border_fedex_freight?(delivery)
  #   status_message_arr << "Delivery does not have any labeled shipments" if !any_labeled_shipments
  #   status_message_arr << "Not all shipments have tracking numbers" if !all_labeled_shipments_have_tracking_number
  #   status_message = "#{status_message_arr.join('. ')}."
  # end
  ############################################################################
  # Note that we are disabling LTL shipping insurance for all deliveries based on price increases, lack of claims and an executive decision by Christian Billen and Venu Adepu on 1/30/2025
  ############################################################################
  status = :error
  status_message = 'LTL shipping insurance is now disabled.'
  Result.new(status: status, status_message: status_message)
end

#qualifies_for_rating?(delivery) ⇒ Boolean

Returns:

  • (Boolean)


136
137
138
139
140
141
142
143
144
145
146
# File 'app/services/shipping/ltl_shipping_insurance.rb', line 136

def qualifies_for_rating?(delivery)
  # here we want to return a simple TRUE/FALSE boolean, this is to see if we can use this when the delivery, in quoting status, gets ship-labeled
  # return delivery.supported_shipping_carrier? && delivery_carrier_present?(delivery) && (delivery.calculate_declared_value.to_f.abs > INSURED_VALUE_MIN) && !cross_border_fedex_freight?(delivery) && !bill_shipping_to_customer_and_not_a_store_transfer?(delivery)
  ############################################################################
  # Note that we are disabling LTL shipping insurance for all deliveries based on price increases, lack of claims and an executive decision by Christian Billen and Venu Adepu on 1/30/2025
  ############################################################################
  # return false
  # 03/18/24 Here we have decided per Venu and Christian to discontinue our Shipping Insurance with Loadsure, and self-insure after seeing no record of any claims or losses for LTL shipments in our history.
  # So we want to qualify all LTL shipments i.e. return TRUE, so we don't pass declared value to the LTL carriers, but then do nothing when it is ship insurance time
  true
end

#void_shipping_insurance_for_delivery(delivery) ⇒ Object



229
230
231
232
233
234
235
236
237
238
239
240
241
242
# File 'app/services/shipping/ltl_shipping_insurance.rb', line 229

def void_shipping_insurance_for_delivery(delivery)
  status = :ok
  status_message = ''

  void_result_hash = HTTP.use(logging: { logger: Rails.logger }).timeout(60).auth("Bearer #{LOADSURE_API_HASH[:token]}").post("#{LOADSURE_API_HASH[:base_url]}/cancelCertificate", json: void_data(delivery)).parse.with_indifferent_access
  if void_result_hash[:success] == false
    msgs = (void_result_hash[:errors] || []).map { |e| e.dig(:message) }
    status = :error
    status_message = msgs.join('. ')
  end

  delivery.shipments.update_all(shipping_insurance_data: {}.merge(void_result_hash)) if status == :ok
  Result.new(status: status, status_message: status_message)
end