Class: WarehousePackage

Inherits:
ApplicationRecord show all
Includes:
Models::Auditable
Defined in:
app/models/warehouse_package.rb

Overview

== Schema Information

Table name: warehouse_packages
Database name: primary

id :integer not null, primary key
box_type :string(255)
description :string(255)
fake_stopgap :boolean
flat_rate_package_type :string
height :float
is_oversize :boolean
length :float
packagings_count :integer default(0), not null
sort_order :integer
supplier_name :string
supplier_part_number :string
volume :float
width :float
created_at :datetime
updated_at :datetime
creator_id :integer
store_id :integer
updater_id :integer

Indexes

idx_height_store_id (height,store_id)
index_warehouse_packages_on_flat_rate_package_type (flat_rate_package_type)
index_warehouse_packages_on_is_oversize (is_oversize)
index_warehouse_packages_on_store_id (store_id)

Foreign Keys

warehouse_packages_store_id_fk (store_id => stores.id) ON DELETE => cascade

Constant Summary collapse

BOXTYPES =
["box", "pallet", "cylinder", "crate"]

Constants included from Models::Auditable

Models::Auditable::ALWAYS_IGNORED

Belongs to collapse

Methods included from Models::Auditable

#creator, #updater

Has many collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Models::Auditable

#all_skipped_columns, #audit_reference_data, #should_not_save_version, #stamp_record

Methods inherited from ApplicationRecord

ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation

Methods included from Models::EventPublishable

#publish_event

Class Method Details

.activeActiveRecord::Relation<WarehousePackage>

A relation of WarehousePackages that are active. Active Record Scope

Returns:

See Also:



44
# File 'app/models/warehouse_package.rb', line 44

scope :active, -> { where("warehouse_packages.fake_stopgap IS NOT TRUE") }

.box_type_options_for_selectObject



132
133
134
# File 'app/models/warehouse_package.rb', line 132

def self.box_type_options_for_select
  BOXTYPES.map{|p| [p.humanize, p]}
end

.by_store_idActiveRecord::Relation<WarehousePackage>

A relation of WarehousePackages that are by store id. Active Record Scope

Returns:

See Also:



43
# File 'app/models/warehouse_package.rb', line 43

scope :by_store_id, ->(store_id) { where(:store_id => store_id).order("sort_order ASC, volume ASC") }

.check_if_dimensions_oversize(dimensions, container_type = Shipment.container_types.keys.first, carrier: nil) ⇒ Object

Check if dimensions would be oversize for a specific carrier

Parameters:

  • dimensions (Array<Numeric>)

    Array of [length, width, height]

  • container_type (String) (defaults to: Shipment.container_types.keys.first)

    Container type

  • carrier (String, nil) (defaults to: nil)

    Optional carrier name



91
92
93
94
95
# File 'app/models/warehouse_package.rb', line 91

def self.check_if_dimensions_oversize(dimensions, container_type = Shipment.container_types.keys.first, carrier: nil)
  dim_length, dim_width, dim_height = dimensions.sort.reverse
  pkg = Shipping::Container.new(length: dim_length, width: dim_width, height: dim_height, container_type:)
  check_oversize?(pkg, carrier:)
end

.check_oversize?(pkg, carrier: nil) ⇒ Boolean

Check if a package is oversize for a specific carrier (or all carriers if nil)

Parameters:

  • pkg (Shipping::Container)

    The package to check

  • carrier (String, nil) (defaults to: nil)

    Optional carrier name (e.g., 'UPS', 'FedEx', 'Purolator', 'Canpar')

Returns:

  • (Boolean)


80
81
82
83
84
85
# File 'app/models/warehouse_package.rb', line 80

def self.check_oversize?(pkg, carrier: nil)
  r = Shipping::PackageAuditor.new
  carriers = carrier.present? ? [carrier] : nil
  r.process(pkg, carriers:)
  r.oversize_conditions?
end

.default_sortActiveRecord::Relation<WarehousePackage>

A relation of WarehousePackages that are default sort. Active Record Scope

Returns:

See Also:



45
# File 'app/models/warehouse_package.rb', line 45

scope :default_sort, -> { order("sort_order ASC, volume ASC") }

.find_or_create_closest_package_within_tolerance(store_id, package_dims, tolerance = 0.1, short_desc = "", fake_stopgap = nil) ⇒ Object



144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'app/models/warehouse_package.rb', line 144

def self.find_or_create_closest_package_within_tolerance(store_id, package_dims, tolerance=0.1, short_desc="", fake_stopgap=nil)
  sorted_dims = package_dims.sort_by{|d| d.to_f.ceil}
  # find matching warehouse package in this store (assume length is longest dimension then width, then height)
  warehouse_package = WarehousePackage.by_store_id(store_id).where(:length => sorted_dims[2].to_f, :width => sorted_dims[1].to_f, :height => sorted_dims[0].to_f).first
  unless warehouse_package
    # if no exact match, find closest warehouse package by volume and see if its dimensions are within tolerance of 10%
    target_vol = sorted_dims[2]*sorted_dims[1]*sorted_dims[0].to_f
    WarehousePackage.by_store_id(store_id).sort_by{|whp| whp.volume.to_f}.each_with_index do |whp, i|
      if (whp.volume.to_f/target_vol - 1.0).abs < tolerance
        # found one close
        if (whp.length/sorted_dims[2] - 1.0).abs < tolerance and (whp.width/sorted_dims[1] - 1.0).abs < tolerance and (whp.height/sorted_dims[0] - 1.0).abs < tolerance
          warehouse_package = whp
          break
        end
      end
    end
  end
  unless warehouse_package
    warehouse_package = WarehousePackage.create({:description => "#{sorted_dims[2].to_i}x#{sorted_dims[1].to_i}x#{sorted_dims[0].to_i} #{short_desc}", :store_id => store_id, :length => sorted_dims[2].to_f, :width => sorted_dims[1].to_f, :height => sorted_dims[0].to_f, :fake_stopgap => fake_stopgap})
  end
  return warehouse_package
end

.flat_rate_package_types_for_selectObject



59
60
61
# File 'app/models/warehouse_package.rb', line 59

def self.flat_rate_package_types_for_select
  %w(FlatRatePaddedEnvelope FlatRateEnvelope MediumFlatRateBox RegionalRateBoxA LargeFlatRateBox SmallFlatRateEnvelope)
end

.options_for_select(store_id) ⇒ Object



55
56
57
# File 'app/models/warehouse_package.rb', line 55

def self.options_for_select(store_id)
  WarehousePackage.active.by_store_id(store_id).sort_by{|p| [(p.flat_rate_package_type.present? ? 0 : 1), p.volume, p.description]}.map{|p| ["#{p.description}#{", oversize" if p.is_oversize}", p.id]}
end

.sort_order_options_for_selectObject



136
137
138
139
140
141
142
# File 'app/models/warehouse_package.rb', line 136

def self.sort_order_options_for_select
  num_arr = []
  WarehousePackage.count.times do |i|
    num_arr << i
  end
  [["Autosort By Volume", nil]].concat(num_arr.map{|n| [n.to_s, n]})
end

Instance Method Details

#can_contain?(warehouse_package, tolerance = 0.0) ⇒ Boolean

fraction so 0.1 = within 10%

Returns:

  • (Boolean)


101
102
103
# File 'app/models/warehouse_package.rb', line 101

def can_contain?(warehouse_package, tolerance = 0.0) # fraction so 0.1 = within 10%
  return self.can_contain_dimensions?([warehouse_package.length, warehouse_package.width, warehouse_package.height], tolerance)
end

#can_contain_dimensions?(dimensions, tolerance = 0.0) ⇒ Boolean

fraction so 0.1 = within 10%

Returns:

  • (Boolean)


111
112
113
114
115
# File 'app/models/warehouse_package.rb', line 111

def can_contain_dimensions?(dimensions, tolerance = 0.0) # fraction so 0.1 = within 10%
  dim_length, dim_width, dim_height = dimensions.sort_by{|d| d.to_f}.reverse
  contains = (dim_length*(1.0 - tolerance) <= self.length) && (dim_width*(1.0 - tolerance) <= self.width) && (dim_height*(1.0 - tolerance) <= self.height)
  return contains
end

#check_oversize?(carrier: nil) ⇒ Boolean

Check if this package is oversize for a specific carrier (or all carriers if nil)

Parameters:

  • carrier (String, nil) (defaults to: nil)

    Optional carrier name (e.g., 'UPS', 'FedEx', 'Purolator', 'Canpar')

Returns:

  • (Boolean)


73
74
75
# File 'app/models/warehouse_package.rb', line 73

def check_oversize?(carrier: nil)
  self.class.check_oversize?(shipping_dimensions_to_package, carrier:)
end

#container_typeObject



181
182
183
184
185
186
187
188
189
190
# File 'app/models/warehouse_package.rb', line 181

def container_type
  # This method basically maps to Shipment container types
  if box_type == 'pallet'
   Shipment.container_types.keys[1] # 'pallet'
  elsif box_type == 'crate'
   Shipment.container_types.keys[2] # 'crate'
  else
   Shipment.container_types.keys.first # 'carton'
  end
end

#fits_inside?(warehouse_package, tolerance = 0.0) ⇒ Boolean

fraction so 0.1 = within 10%

Returns:

  • (Boolean)


97
98
99
# File 'app/models/warehouse_package.rb', line 97

def fits_inside?(warehouse_package, tolerance = 0.0) # fraction so 0.1 = within 10%
  return self.fits_inside_dimensions?([warehouse_package.length, warehouse_package.width, warehouse_package.height], tolerance)
end

#fits_inside_dimensions?(dimensions, tolerance = 0.0) ⇒ Boolean

fraction so 0.1 = within 10%

Returns:

  • (Boolean)


105
106
107
108
109
# File 'app/models/warehouse_package.rb', line 105

def fits_inside_dimensions?(dimensions, tolerance = 0.0) # fraction so 0.1 = within 10%
  dim_length, dim_width, dim_height = dimensions.sort_by{|d| d}.reverse
  fits = (dim_length.to_f >= self.length*(1.0 - tolerance)) && (dim_width.to_f >= self.width*(1.0 - tolerance)) && (dim_height.to_f >= self.height*(1.0 - tolerance))
  return fits
end

#girthObject



173
174
175
# File 'app/models/warehouse_package.rb', line 173

def girth
  2.0*(width+height)
end

#length_plus_girthObject



177
178
179
# File 'app/models/warehouse_package.rb', line 177

def length_plus_girth
  (length + girth).ceil
end

#ok_to_destroy?Boolean

Returns:

  • (Boolean)


63
64
65
# File 'app/models/warehouse_package.rb', line 63

def ok_to_destroy?
  packagings.blank?
end

#packagingsActiveRecord::Relation<Packaging>

Returns:

See Also:



41
# File 'app/models/warehouse_package.rb', line 41

has_many :packagings, dependent: :destroy

#set_dimensions_volume_and_box_typeObject



117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'app/models/warehouse_package.rb', line 117

def set_dimensions_volume_and_box_type
  # always set largest dimension to length, then width, then height
  dims = [length, width, height].sort.map(&:presence).reverse
  if dims.size == 3 # we have complete dimensions
    self.length, self.width, self.height = dims
    self.volume = length * width * height
    desc_prepend = "#{length.round}x#{width.round}x#{height.round}"
    self.description = desc_prepend+", "+self.description.to_s unless self.description.to_s.index(desc_prepend)
    self.is_oversize = check_oversize?
  end
  # just assume box geometry for now
  self.box_type ||= "box"
  return true
end

#shipping_dimensions_to_packageObject



67
68
69
# File 'app/models/warehouse_package.rb', line 67

def shipping_dimensions_to_package
  Shipping::Container.new(length: length, width: width, height: height, container_type: container_type)
end

#short_descriptionObject



167
168
169
170
171
# File 'app/models/warehouse_package.rb', line 167

def short_description
  d = [length, width, height].compact.map{|u| "#{u}\""}.join(' x ')
  d << " [Oversize]" if is_oversize
  d
end

#storeStore

Returns:

See Also:



40
# File 'app/models/warehouse_package.rb', line 40

belongs_to :store, optional: true